ExactBodyMatch performs exact body matching with noise awareness. First pass: fast string equality. Second pass: noise-aware comparison that skips obfuscated fields identified by Mock.Noise patterns. Three body shapes are handled in the second pass: 1. A mock body that is itself entirely a noise val
(body []byte, schemaMatched []*models.Mock)
| 608 | // produce byte-identical request bodies at replay. |
| 609 | // 3. JSON — field-by-field comparison via JSONBodyMatchScore. |
| 610 | func (h *HTTP) ExactBodyMatch(body []byte, schemaMatched []*models.Mock) (bool, *models.Mock) { |
| 611 | // Log all mock names in a single line for better readability |
| 612 | mockNames := make([]string, len(schemaMatched)) |
| 613 | for i, mock := range schemaMatched { |
| 614 | mockNames[i] = mock.Name |
| 615 | } |
| 616 | h.Logger.Debug("mocks under consideration for exact body match", zap.Strings("mock names", mockNames), zap.String("req body", string(body))) |
| 617 | |
| 618 | // First pass: exact string match (fastest path) |
| 619 | for _, mock := range schemaMatched { |
| 620 | if mock.Spec.HTTPReq.Body == string(body) { |
| 621 | h.Logger.Debug("http mock matched", |
| 622 | zap.String("mock", mock.Name), |
| 623 | zap.Float64("match_percentage", 100.0), |
| 624 | zap.String("match_type", "exact_body")) |
| 625 | return true, mock |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | // Second pass: noise-aware match for mocks with obfuscated values. |
| 630 | // Pre-compute request-side properties once so we don't re-derive them per |
| 631 | // candidate mock: JSON unmarshaling for the JSON-noise path, and the |
| 632 | // form-encoded heuristic (which itself runs url.ParseQuery) for the |
| 633 | // form-body path. |
| 634 | reqBodyStr := string(body) |
| 635 | isReqJSON := pkg.IsJSON(body) |
| 636 | var reqData interface{} |
| 637 | if isReqJSON { |
| 638 | if err := json.Unmarshal(body, &reqData); err != nil { |
| 639 | isReqJSON = false |
| 640 | } |
| 641 | } |
| 642 | reqIsForm := !isReqJSON && looksLikeFormEncoded(reqBodyStr) |
| 643 | |
| 644 | for _, mock := range schemaMatched { |
| 645 | nc := util.NewNoiseChecker(mock.Noise) |
| 646 | if nc == nil { |
| 647 | continue // no noise patterns → already checked in first pass |
| 648 | } |
| 649 | |
| 650 | mockBody := mock.Spec.HTTPReq.Body |
| 651 | |
| 652 | // If the entire body is a single noisy value, auto-match |
| 653 | // (schema match already filtered by URL, method, headers) |
| 654 | if nc.IsNoisy(mockBody) { |
| 655 | h.Logger.Debug("http mock matched", |
| 656 | zap.String("mock", mock.Name), |
| 657 | zap.Float64("match_percentage", 100.0), |
| 658 | zap.Int("noisy_fields_skipped", 1), |
| 659 | zap.String("match_type", "exact_body_fully_noisy")) |
| 660 | return true, mock |
| 661 | } |
| 662 | |
| 663 | // Form-encoded noise-aware comparison. Each "key=value" segment is |
| 664 | // tested against the mock's noise patterns; a match wildcards that |
| 665 | // segment. The remaining keys must be the same set on both sides |
| 666 | // and each non-wildcarded value(s) must be byte-equal. |
| 667 | if reqIsForm && looksLikeFormEncoded(mockBody) { |