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 }