| 706 | } |
| 707 | |
| 708 | func SimulateHTTP(ctx context.Context, tc *models.TestCase, testSet string, logger *zap.Logger, cfg SimulationConfig) (*models.HTTPResp, error) { |
| 709 | templatedResponse := tc.HTTPResp // keep a copy of the original templatized response |
| 710 | |
| 711 | logger.Info("starting test for", zap.Any("test case", models.HighlightString(tc.Name)), zap.Any("test set", models.HighlightString(testSet))) |
| 712 | |
| 713 | // Prepare the HTTP request using the shared helper |
| 714 | cfg.RequestTimeout = time.Second * time.Duration(cfg.APITimeout) |
| 715 | prepared, err := prepareHTTPRequest(ctx, tc, testSet, logger, cfg) |
| 716 | if err != nil { |
| 717 | return nil, err |
| 718 | } |
| 719 | |
| 720 | logger.Debug(fmt.Sprintf("Sending request to user app:%v", prepared.Request)) |
| 721 | |
| 722 | // Execute the request (re-sending only on a pre-response connection-refused) |
| 723 | httpResp, errHTTPReq := doRequestWithConnRefusedRetry(ctx, logger, prepared.Client, prepared.Request) |
| 724 | if errHTTPReq != nil { |
| 725 | utils.LogError(logger, errHTTPReq, "failed to send testcase request to app") |
| 726 | return nil, errHTTPReq |
| 727 | } |
| 728 | |
| 729 | defer func() { |
| 730 | if httpResp != nil && httpResp.Body != nil { |
| 731 | if err := httpResp.Body.Close(); err != nil { |
| 732 | utils.LogError(logger, err, "failed to close response body") |
| 733 | } |
| 734 | } |
| 735 | }() |
| 736 | |
| 737 | // Read full response body |
| 738 | respBody, errReadRespBody := io.ReadAll(httpResp.Body) |
| 739 | if errReadRespBody != nil { |
| 740 | utils.LogError(logger, errReadRespBody, "failed reading response body") |
| 741 | return nil, errReadRespBody |
| 742 | } |
| 743 | |
| 744 | // Decompress if needed |
| 745 | if httpResp.Header.Get("Content-Encoding") != "" { |
| 746 | respBody, err = Decompress(logger, httpResp.Header.Get("Content-Encoding"), respBody) |
| 747 | if err != nil { |
| 748 | utils.LogError(logger, err, "failed to decode response body") |
| 749 | return nil, err |
| 750 | } |
| 751 | } |
| 752 | |
| 753 | statusMessage := http.StatusText(httpResp.StatusCode) |
| 754 | |
| 755 | resp := &models.HTTPResp{ |
| 756 | StatusCode: httpResp.StatusCode, |
| 757 | StatusMessage: statusMessage, |
| 758 | Body: string(respBody), |
| 759 | Header: ToYamlHTTPHeader(httpResp.Header), |
| 760 | } |
| 761 | |
| 762 | // Centralized template update: walk the response body and headers to refresh template values. |
| 763 | // NOTE: header-only updates are important for e.g. listmonk where the login 302 has an empty |
| 764 | // body but carries a fresh Set-Cookie: session=... that every subsequent test consumes. |
| 765 | if len(respBody) > 0 || len(resp.Header) > 0 { |