(expectedResp models.HTTPResp, stream io.Reader, boundary string, jsonNoiseKeys map[string]struct{}, logger *zap.Logger)
| 1665 | } |
| 1666 | |
| 1667 | func compareMultipartStream(expectedResp models.HTTPResp, stream io.Reader, boundary string, jsonNoiseKeys map[string]struct{}, logger *zap.Logger) (bool, string, *StreamMismatchInfo, error) { |
| 1668 | if strings.TrimSpace(boundary) == "" { |
| 1669 | return false, "", nil, fmt.Errorf("missing multipart boundary for stream comparison") |
| 1670 | } |
| 1671 | |
| 1672 | expectedBody := expectedResp.Body |
| 1673 | expectedQueue, err := parseMultipartQueue(strings.NewReader(expectedBody), boundary) |
| 1674 | if err != nil { |
| 1675 | return false, "", nil, err |
| 1676 | } |
| 1677 | |
| 1678 | actualQueue := make([]string, 0, len(expectedQueue)) |
| 1679 | nextExpected := 0 |
| 1680 | reader := multipart.NewReader(stream, boundary) |
| 1681 | |
| 1682 | for { |
| 1683 | part, partErr := reader.NextPart() |
| 1684 | if partErr == io.EOF { |
| 1685 | break |
| 1686 | } |
| 1687 | if partErr != nil { |
| 1688 | return false, strings.Join(actualQueue, "\n\n--PART--\n\n"), nil, partErr |
| 1689 | } |
| 1690 | |
| 1691 | actualPart, partReadErr := readMultipartPart(part) |
| 1692 | _ = part.Close() |
| 1693 | if partReadErr != nil { |
| 1694 | return false, strings.Join(actualQueue, "\n\n--PART--\n\n"), nil, partReadErr |
| 1695 | } |
| 1696 | |
| 1697 | if nextExpected >= len(expectedQueue) { |
| 1698 | logger.Debug("received additional multipart stream data after expected stream was fully matched; closing stream capture", |
| 1699 | zap.Int("expected_parts", len(expectedQueue))) |
| 1700 | break |
| 1701 | } |
| 1702 | |
| 1703 | expected := expectedQueue[nextExpected] |
| 1704 | actualQueue = append(actualQueue, actualPart.describe()) |
| 1705 | ok, reason := compareMultipartPart(expected, actualPart, jsonNoiseKeys) |
| 1706 | if !ok { |
| 1707 | logger.Debug("multipart stream mismatch", |
| 1708 | zap.Int("part_index", nextExpected), |
| 1709 | zap.String("reason", reason), |
| 1710 | zap.String("expected_part", expected.describe()), |
| 1711 | zap.String("actual_part", actualPart.describe())) |
| 1712 | mismatchInfo := &StreamMismatchInfo{ |
| 1713 | FrameIndex: nextExpected, |
| 1714 | ExpectedFrame: expected.describe(), |
| 1715 | ActualFrame: actualPart.describe(), |
| 1716 | Reason: reason, |
| 1717 | } |
| 1718 | return false, strings.Join(actualQueue, "\n\n--PART--\n\n"), mismatchInfo, nil |
| 1719 | } |
| 1720 | |
| 1721 | nextExpected++ |
| 1722 | if nextExpected == len(expectedQueue) { |
| 1723 | logger.Debug("all expected multipart parts matched; closing stream capture early to avoid waiting for extra stream parts", |
| 1724 | zap.Int("matched_parts", nextExpected)) |
no test coverage detected