|
4 | 4 | "encoding/json"
|
5 | 5 | "fmt"
|
6 | 6 | "mime"
|
7 |
| - "reflect" |
8 | 7 | "regexp"
|
9 | 8 | "strings"
|
10 | 9 | "sync"
|
@@ -118,18 +117,8 @@ func LoadInteraction(data []byte, alias string) (*Interaction, error) {
|
118 | 117 |
|
119 | 118 | switch mediaType {
|
120 | 119 | case mediaTypeJSON:
|
121 |
| - if jsonRequestBody, ok := requestBody.(map[string]interface{}); ok { |
122 |
| - interaction.addJSONConstraintsFromPact("$.body", propertiesWithMatchingRule, jsonRequestBody) |
123 |
| - return interaction, nil |
124 |
| - } |
125 |
| - |
126 |
| - if _, ok := requestBody.([]interface{}); ok { |
127 |
| - // An array request body should be accepted for application/json media type. |
128 |
| - // However, no constraint is added for it |
129 |
| - return interaction, nil |
130 |
| - } |
131 |
| - |
132 |
| - return nil, fmt.Errorf("media type is %s but body is not json", mediaType) |
| 120 | + interaction.addJSONConstraintsFromPact("$.body", propertiesWithMatchingRule, requestBody) |
| 121 | + return interaction, nil |
133 | 122 | case mediaTypeText, mediaTypeCsv, mediaTypeXml:
|
134 | 123 | if body, ok := requestBody.(string); ok {
|
135 | 124 | interaction.addTextConstraintsFromPact(propertiesWithMatchingRule, body)
|
@@ -202,6 +191,8 @@ func getPathRegex(matchingRules map[string]interface{}) (string, error) {
|
202 | 191 | return regexString, nil
|
203 | 192 | }
|
204 | 193 |
|
| 194 | +// Gets the pact JSON file style matching rules from the "matchingRules" property of the request. |
| 195 | +// Note that Pact DSL style matching rules within the body are identified later when adding JSON constraints. |
205 | 196 | func getMatchingRules(request map[string]interface{}) map[string]interface{} {
|
206 | 197 | rules, hasRules := request["matchingRules"]
|
207 | 198 | if !hasRules {
|
@@ -270,24 +261,37 @@ func parseMediaType(request map[string]interface{}) (string, error) {
|
270 | 261 |
|
271 | 262 | // This function adds constraints for all the fields in the JSON request body which do not
|
272 | 263 | // have a corresponding matching rule
|
273 |
| -func (i *Interaction) addJSONConstraintsFromPact(path string, matchingRules map[string]bool, values map[string]interface{}) { |
274 |
| - for k, v := range values { |
275 |
| - switch val := v.(type) { |
276 |
| - case map[string]interface{}: |
277 |
| - if _, exists := val["json_class"]; exists { |
278 |
| - continue |
279 |
| - } |
280 |
| - i.addJSONConstraintsFromPact(path+"."+k, matchingRules, val) |
281 |
| - default: |
282 |
| - p := path + "." + k |
283 |
| - if _, hasRule := matchingRules[p]; !hasRule { |
284 |
| - i.AddConstraint(interactionConstraint{ |
285 |
| - Path: p, |
286 |
| - Format: "%v", |
287 |
| - Values: []interface{}{val}, |
288 |
| - }) |
289 |
| - } |
| 264 | +func (i *Interaction) addJSONConstraintsFromPact(path string, matchingRules map[string]bool, value interface{}) { |
| 265 | + if _, hasRule := matchingRules[path]; hasRule { |
| 266 | + return |
| 267 | + } |
| 268 | + switch val := value.(type) { |
| 269 | + case map[string]interface{}: |
| 270 | + // json_class is used to test for a Pact DSL-style matching rule within the body. The matchingRules passed |
| 271 | + // to this method will not include these. |
| 272 | + if _, exists := val["json_class"]; exists { |
| 273 | + return |
290 | 274 | }
|
| 275 | + for k, v := range val { |
| 276 | + i.addJSONConstraintsFromPact(path+"."+k, matchingRules, v) |
| 277 | + } |
| 278 | + case []interface{}: |
| 279 | + // Create constraints for each element in the array. This allows matching rules to override them. |
| 280 | + for j := range val { |
| 281 | + i.addJSONConstraintsFromPact(fmt.Sprintf("%s[%d]", path, j), matchingRules, val[j]) |
| 282 | + } |
| 283 | + // Length constraint so that requests with additional elements at the end of the array will not match |
| 284 | + i.AddConstraint(interactionConstraint{ |
| 285 | + Path: path, |
| 286 | + Format: fmtLen, |
| 287 | + Values: []interface{}{len(val)}, |
| 288 | + }) |
| 289 | + default: |
| 290 | + i.AddConstraint(interactionConstraint{ |
| 291 | + Path: path, |
| 292 | + Format: "%v", |
| 293 | + Values: []interface{}{val}, |
| 294 | + }) |
291 | 295 | }
|
292 | 296 | }
|
293 | 297 |
|
@@ -341,33 +345,27 @@ func (i *Interaction) EvaluateConstraints(request requestDocument, interactions
|
341 | 345 | i.mu.RLock()
|
342 | 346 | defer i.mu.RUnlock()
|
343 | 347 | for _, constraint := range i.constraints {
|
344 |
| - values := constraint.Values |
| 348 | + expected := constraint.Values |
345 | 349 | if constraint.Source != "" {
|
346 | 350 | var err error
|
347 |
| - values, err = i.loadValuesFromSource(constraint, interactions) |
| 351 | + expected, err = i.loadValuesFromSource(constraint, interactions) |
348 | 352 | if err != nil {
|
349 | 353 | violations = append(violations, err.Error())
|
350 | 354 | result = false
|
351 | 355 | continue
|
352 | 356 | }
|
353 | 357 | }
|
354 | 358 |
|
355 |
| - actual := "" |
356 |
| - val, err := jsonpath.Get(request.encodeValues(constraint.Path), map[string]interface{}(request)) |
| 359 | + actual, err := jsonpath.Get(request.encodeValues(constraint.Path), map[string]interface{}(request)) |
357 | 360 | if err != nil {
|
358 |
| - log.Warn(err) |
359 |
| - } |
360 |
| - if reflect.TypeOf(val) == reflect.TypeOf([]interface{}{}) { |
361 |
| - log.Infof("skipping matching on []interface{} type for path '%s'", constraint.Path) |
| 361 | + violations = append(violations, |
| 362 | + fmt.Sprintf("constraint path %q cannot be resolved within request: %q", constraint.Path, err)) |
| 363 | + result = false |
362 | 364 | continue
|
363 | 365 | }
|
364 |
| - if err == nil { |
365 |
| - actual = fmt.Sprintf("%v", val) |
366 |
| - } |
367 | 366 |
|
368 |
| - expected := fmt.Sprintf(constraint.Format, values...) |
369 |
| - if actual != expected { |
370 |
| - violations = append(violations, fmt.Sprintf("value '%s' at path '%s' does not match constraint '%s'", actual, constraint.Path, expected)) |
| 367 | + if err := constraint.check(expected, actual); err != nil { |
| 368 | + violations = append(violations, err.Error()) |
371 | 369 | result = false
|
372 | 370 | }
|
373 | 371 | }
|
|
0 commit comments