github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/testhelpers/httpmock/httpmock.go (about)

     1  package httpmock
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  
    10  	parent "github.com/jarcoal/httpmock"
    11  
    12  	"github.com/ActiveState/cli/internal/fileutils"
    13  	"github.com/ActiveState/cli/internal/logging"
    14  )
    15  
    16  // HTTPMock encapsulate the functionality for mocking requests to a specific base-url.
    17  type HTTPMock struct {
    18  	urlPrefix string
    19  }
    20  
    21  var httpMocks = map[string]*HTTPMock{}
    22  var defaultMock *HTTPMock
    23  
    24  // Activate an and return an *HTTPMock instance. If none yet activated, then the first becomes the default
    25  // HTTPMock; which means you can just call package funcs to use it.
    26  func Activate(prefix string) *HTTPMock {
    27  	urlPrefix := strings.TrimSuffix(prefix, "/")
    28  	if _, mockExists := httpMocks[urlPrefix]; mockExists {
    29  		logging.Warning("Activating http mock for a prefix that is already in use, this could cause issues. Prefix=%s", urlPrefix)
    30  	}
    31  
    32  	mock := &HTTPMock{urlPrefix: urlPrefix}
    33  	httpMocks[urlPrefix] = mock
    34  
    35  	if defaultMock == nil {
    36  		defaultMock = mock
    37  		parent.Activate()
    38  	}
    39  
    40  	return mock
    41  }
    42  
    43  // DeActivate the httpmock
    44  func DeActivate() {
    45  	defer parent.DeactivateAndReset()
    46  	defaultMock = nil
    47  	httpMocks = map[string]*HTTPMock{}
    48  }
    49  
    50  // Register registers a httpmock for the given request (the response file is based on the request)
    51  func (mock *HTTPMock) Register(method string, request string) {
    52  	mock.RegisterWithCode(method, request, 200)
    53  }
    54  
    55  // RegisterWithCode is the same as Register but it allows specifying a code
    56  func (mock *HTTPMock) RegisterWithCode(method string, request string, code int) {
    57  	responseFile := strings.Replace(request, mock.urlPrefix, "", 1)
    58  	mock.RegisterWithResponse(method, request, code, responseFile)
    59  }
    60  
    61  // RegisterWithResponse is the same as RegisterWithCode but it allows specifying a response file
    62  func (mock *HTTPMock) RegisterWithResponse(method string, request string, code int, responseFile string) {
    63  	responsePath := getResponsePath()
    64  	responseFile = getResponseFile(method, code, responseFile, responsePath)
    65  	request = strings.TrimSuffix(mock.urlPrefix+"/"+strings.TrimPrefix(request, "/"), "/")
    66  	parent.RegisterResponder(method, request,
    67  		parent.NewStringResponder(code, string(fileutils.ReadFileUnsafe(responseFile))))
    68  }
    69  
    70  // RegisterWithResponseBody will respond with the given code and responseBody, no external files involved
    71  func (mock *HTTPMock) RegisterWithResponseBody(method string, request string, code int, responseBody string) {
    72  	mock.RegisterWithResponseBytes(method, request, code, []byte(responseBody))
    73  }
    74  
    75  // RegisterWithResponseBytes will respond with the given code and responseBytes, no external files involved
    76  func (mock *HTTPMock) RegisterWithResponseBytes(method string, request string, code int, responseBytes []byte) {
    77  	request = strings.TrimSuffix(mock.urlPrefix+"/"+strings.TrimPrefix(request, "/"), "/")
    78  	parent.RegisterResponder(method, request,
    79  		parent.NewBytesResponder(code, responseBytes))
    80  }
    81  
    82  // RegisterWithResponderBody register a httpmock with a custom responder that returns the response body
    83  func (mock *HTTPMock) RegisterWithResponderBody(method string, request string, cb func(req *http.Request) (int, string)) {
    84  	request = strings.TrimSuffix(mock.urlPrefix+"/"+strings.TrimPrefix(request, "/"), "/")
    85  	parent.RegisterResponder(method, request, func(req *http.Request) (*http.Response, error) {
    86  		code, responseData := cb(req)
    87  		return parent.NewStringResponse(code, responseData), nil
    88  	})
    89  }
    90  
    91  // RegisterWithResponder register a httpmock with a custom responder
    92  func (mock *HTTPMock) RegisterWithResponder(method string, request string, cb func(req *http.Request) (int, string)) {
    93  	request = strings.TrimSuffix(mock.urlPrefix+"/"+strings.TrimPrefix(request, "/"), "/")
    94  	logging.Debug("Mocking: %s", request)
    95  	responsePath := getResponsePath()
    96  	parent.RegisterResponder(method, request, func(req *http.Request) (*http.Response, error) {
    97  		code, responseFile := cb(req)
    98  		responseFile = getResponseFile(method, code, responseFile, responsePath)
    99  		responseData := string(fileutils.ReadFileUnsafe(responseFile))
   100  		return parent.NewStringResponse(code, responseData), nil
   101  	})
   102  }
   103  
   104  func getResponseFile(method string, code int, responseFile string, responsePath string) string {
   105  	responseFile = fmt.Sprintf("%s-%s", strings.ToUpper(method), strings.TrimPrefix(responseFile, "/"))
   106  	if code != 200 {
   107  		responseFile = fmt.Sprintf("%s-%d", responseFile, code)
   108  	}
   109  	ext := ".json"
   110  	if filepath.Ext(responseFile) != "" {
   111  		ext = ""
   112  	}
   113  	responseFile = filepath.Join(responsePath, responseFile) + ext
   114  
   115  	return responseFile
   116  }
   117  
   118  func getResponsePath() string {
   119  	_, currentFile, _, _ := runtime.Caller(0)
   120  	file := currentFile
   121  	ok := true
   122  	iter := 2
   123  
   124  	for file == currentFile && ok {
   125  		_, file, _, ok = runtime.Caller(iter)
   126  		iter = iter + 1
   127  	}
   128  
   129  	if file == "" || file == currentFile {
   130  		panic("Could not get caller")
   131  	}
   132  
   133  	return filepath.Join(filepath.Dir(file), "testdata", "httpresponse")
   134  }
   135  
   136  func ensureDefaultMock() {
   137  	if defaultMock == nil {
   138  		panic("default HTTPMock is not defined")
   139  	}
   140  }
   141  
   142  // Register defers to the default HTTPMock's Register or errors if no default defined.
   143  func Register(method string, request string) {
   144  	ensureDefaultMock()
   145  	defaultMock.Register(method, request)
   146  }
   147  
   148  // RegisterWithCode defers to the default HTTPMock's RegisterWithCode or errors if no default defined.
   149  func RegisterWithCode(method string, request string, code int) {
   150  	ensureDefaultMock()
   151  	defaultMock.RegisterWithCode(method, request, code)
   152  }
   153  
   154  // RegisterWithResponse defers to the default HTTPMock's RegisterWithResponse or errors if no default defined.
   155  func RegisterWithResponse(method string, request string, code int, responseFile string) {
   156  	ensureDefaultMock()
   157  	defaultMock.RegisterWithResponse(method, request, code, responseFile)
   158  }
   159  
   160  // RegisterWithResponseBody defers to the default HTTPMock's RegisterWithResponseBody or errors if no default defined.
   161  func RegisterWithResponseBody(method string, request string, code int, responseBody string) {
   162  	ensureDefaultMock()
   163  	defaultMock.RegisterWithResponseBody(method, request, code, responseBody)
   164  }
   165  
   166  // RegisterWithResponseBytes defers to the default HTTPMock's RegisterWithResponseBytes or errors if no default defined.
   167  func RegisterWithResponseBytes(method string, request string, code int, responseBytes []byte) {
   168  	ensureDefaultMock()
   169  	defaultMock.RegisterWithResponseBytes(method, request, code, responseBytes)
   170  }
   171  
   172  // RegisterWithResponderBody defers to the default HTTPMock's RegisterWithResponderBody or errors if no default defined.
   173  func RegisterWithResponderBody(method string, request string, code int, cb func(req *http.Request) (int, string)) {
   174  	ensureDefaultMock()
   175  	defaultMock.RegisterWithResponderBody(method, request, cb)
   176  }
   177  
   178  // RegisterWithResponder defers to the default HTTPMock's RegisterWithResponder or errors if no default defined.
   179  func RegisterWithResponder(method string, request string, cb func(req *http.Request) (int, string)) {
   180  	ensureDefaultMock()
   181  	defaultMock.RegisterWithResponder(method, request, cb)
   182  }