github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/features/bootstrap/context_request.go (about) 1 package bootstrap 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "strings" 10 "time" 11 12 "github.com/cucumber/godog" 13 "github.com/cucumber/messages-go/v10" 14 jwtGo "github.com/dgrijalva/jwt-go" 15 "github.com/tidwall/gjson" 16 17 "github.com/hellofresh/janus/pkg/config" 18 "github.com/hellofresh/janus/pkg/jwt" 19 ) 20 21 const ( 22 headerAuthorization = "Authorization" 23 ) 24 25 // RegisterRequestContext registers godog suite context for handling HTTP-requests related steps 26 func RegisterRequestContext(ctx *godog.ScenarioContext, port, apiPort, portSecondary, apiPortSecondary, defaultUpstreamsPort, upstreamsPort int, adminCred config.Credentials) { 27 scenarioCtx := &requestContext{ 28 port: port, 29 apiPort: apiPort, 30 portSecondary: portSecondary, 31 apiPortSecondary: apiPortSecondary, 32 adminCred: adminCred, 33 34 defaultUpstreamsHost: fmt.Sprintf("/localhost:%d/", defaultUpstreamsPort), 35 defaultServiceHost: fmt.Sprintf("/{service}:%d/", defaultUpstreamsPort), 36 dynamicUpstreamsHost: fmt.Sprintf("/localhost:%d/", upstreamsPort), 37 dynamicServiceHost: fmt.Sprintf("/{service}:%d/", upstreamsPort), 38 } 39 40 scenarioCtx.requestHeaders = make(http.Header) 41 42 ctx.Step(`^I request "([^"]*)" path with "([^"]*)" method$`, scenarioCtx.iRequestPathWithMethod) 43 ctx.Step(`^I request "([^"]*)" API path with "([^"]*)" method$`, scenarioCtx.iRequestAPIPathWithMethod) 44 ctx.Step(`^I request "([^"]*)" secondary path with "([^"]*)" method$`, scenarioCtx.iRequestSecondaryPathWithMethod) 45 ctx.Step(`^I request "([^"]*)" secondary API path with "([^"]*)" method$`, scenarioCtx.iRequestSecondaryAPIPathWithMethod) 46 ctx.Step(`^I should receive (\d+) response code$`, scenarioCtx.iShouldReceiveResponseCode) 47 ctx.Step(`^header "([^"]*)" should be "([^"]*)"$`, scenarioCtx.headerShouldBe) 48 ctx.Step(`^header "([^"]*)" should start with "([^"]*)"$`, scenarioCtx.headerShouldStartWith) 49 ctx.Step(`^the response should contain "([^"]*)"$`, scenarioCtx.responseShouldContain) 50 ctx.Step(`^response JSON body has "([^"]*)" path with value \'([^']*)\'$`, scenarioCtx.responseJSONBodyHasPathWithValue) 51 ctx.Step(`^response JSON body has "([^"]*)" path and is an array of length (\d+)$`, scenarioCtx.responseJSONBodyHasPathIsAnArrayOfLenght) 52 ctx.Step(`^response JSON body has "([^"]*)" path`, scenarioCtx.responseJSONBodyHasPath) 53 ctx.Step(`^response JSON body is an array of length (\d+)$`, scenarioCtx.responseJSONBodyIsAnArrayOfLength) 54 ctx.Step(`^request JSON payload:$`, scenarioCtx.requestJSONPayload) 55 ctx.Step(`^request header "([^"]*)" is set to "([^"]*)"$`, scenarioCtx.requestHeaderIsSetTo) 56 ctx.Step(`^request JWT token is not set$`, scenarioCtx.requestJWTTokenIsNotSet) 57 ctx.Step(`^request JWT token is valid admin token$`, scenarioCtx.requestJWTTokenIsValidAdminToken) 58 } 59 60 type requestContext struct { 61 port int 62 apiPort int 63 64 portSecondary int 65 apiPortSecondary int 66 67 defaultUpstreamsHost string 68 defaultServiceHost string 69 dynamicUpstreamsHost string 70 dynamicServiceHost string 71 72 adminCred config.Credentials 73 74 requestBody *bytes.Buffer 75 requestHeaders http.Header 76 response *http.Response 77 responseBody []byte 78 } 79 80 func (c *requestContext) iRequestAPIPathWithMethod(path, method string) error { 81 url := fmt.Sprintf("http://localhost:%d%s", c.apiPort, path) 82 return c.doRequest(url, method) 83 } 84 85 func (c *requestContext) iRequestPathWithMethod(path, method string) error { 86 url := fmt.Sprintf("http://localhost:%d%s", c.port, path) 87 return c.doRequest(url, method) 88 } 89 90 func (c *requestContext) iRequestSecondaryAPIPathWithMethod(path, method string) error { 91 url := fmt.Sprintf("http://localhost:%d%s", c.apiPortSecondary, path) 92 return c.doRequest(url, method) 93 } 94 95 func (c *requestContext) iRequestSecondaryPathWithMethod(path, method string) error { 96 url := fmt.Sprintf("http://localhost:%d%s", c.portSecondary, path) 97 return c.doRequest(url, method) 98 } 99 100 func (c *requestContext) doRequest(url, method string) error { 101 var req *http.Request 102 var err error 103 if method == http.MethodGet || method == http.MethodDelete { 104 req, err = http.NewRequest(method, url, nil) 105 } else { 106 req, err = http.NewRequest(method, url, c.requestBody) 107 } 108 if nil != err { 109 return fmt.Errorf("failed to instantiate request instance: %v", err) 110 } 111 112 req.Header = c.requestHeaders 113 114 // Inform to close the connection after the transaction is complete 115 req.Header.Set("Connection", "close") 116 117 c.response, err = http.DefaultClient.Do(req) 118 if err != nil { 119 return fmt.Errorf("failed to perform request: %v", err) 120 } 121 122 c.responseBody, err = ioutil.ReadAll(c.response.Body) 123 if nil != err { 124 return fmt.Errorf("failed to read response body: %v", err) 125 } 126 127 time.Sleep(time.Second) 128 129 return nil 130 } 131 132 func (c *requestContext) iShouldReceiveResponseCode(code int) error { 133 if c.response.StatusCode != code { 134 return fmt.Errorf( 135 "expected response code %d, but actual is %d (response body is: %s)", 136 code, 137 c.response.StatusCode, 138 c.responseBody, 139 ) 140 } 141 142 return nil 143 } 144 145 func (c *requestContext) headerShouldBe(header, value string) error { 146 if actual := c.response.Header.Get(header); actual != value { 147 return fmt.Errorf("expected header %s to be %s, but actual is %s", header, value, actual) 148 } 149 150 return nil 151 152 } 153 154 func (c *requestContext) headerShouldStartWith(header, value string) error { 155 if !strings.HasPrefix(c.response.Header.Get(header), value) { 156 actual := c.response.Header.Get(header) 157 return fmt.Errorf("expected header %s to start with %s, but actual is %s", header, value, actual) 158 } 159 160 return nil 161 162 } 163 164 func (c *requestContext) responseShouldContain(text string) error { 165 if !bytes.Contains(c.responseBody, []byte(text)) { 166 return fmt.Errorf("expected response to contain %s, but actual is %s", text, string(c.responseBody)) 167 } 168 return nil 169 } 170 171 func (c *requestContext) responseJSONBodyHasPathWithValue(path, value string) error { 172 val := gjson.GetBytes(c.responseBody, path) 173 if !val.Exists() { 174 return fmt.Errorf("expected path %s in JSON response, but not found", path) 175 } 176 177 if val.String() != value { 178 return fmt.Errorf("expected path %s in JSON response to be %s, but actual is %s; response: %s", path, value, val.String(), c.responseBody) 179 } 180 181 return nil 182 } 183 184 func (c *requestContext) responseJSONBodyHasPathIsAnArrayOfLenght(path string, length int) error { 185 val := gjson.GetBytes(c.responseBody, path) 186 if !val.Exists() { 187 return fmt.Errorf("expected path %s in JSON response, but not found", path) 188 } 189 190 if !val.IsArray() { 191 return fmt.Errorf("expected path %s in JSON response to be an array, but actual is %s; response: %s", path, val.String(), c.responseBody) 192 } 193 194 v, ok := val.Value().([]interface{}) 195 if !ok { 196 return fmt.Errorf("could not convert array to interface") 197 } 198 199 fmt.Println(val.String()) 200 if len(v) != length { 201 return fmt.Errorf("expected JSON path %s array length is %d, but actual is %d", path, length, len(v)) 202 } 203 204 return nil 205 } 206 207 func (c *requestContext) responseJSONBodyHasPath(path string) error { 208 val := gjson.GetBytes(c.responseBody, path) 209 if !val.Exists() { 210 return fmt.Errorf("expected path %s in JSON response, but not found", path) 211 } 212 213 return nil 214 } 215 216 func (c *requestContext) responseJSONBodyIsAnArrayOfLength(length int) error { 217 var jsonResponse []interface{} 218 err := json.Unmarshal(c.responseBody, &jsonResponse) 219 if nil != err { 220 return fmt.Errorf("failed to unmarshal JSON: %v", err) 221 } 222 223 if len(jsonResponse) != length { 224 return fmt.Errorf("expected JSON response array length is %d, but actual is %d", length, len(jsonResponse)) 225 } 226 227 return nil 228 } 229 230 func (c *requestContext) requestJSONPayload(body *messages.PickleStepArgument_PickleDocString) error { 231 rq := strings.ReplaceAll(body.GetContent(), c.defaultUpstreamsHost, c.dynamicUpstreamsHost) 232 rq = strings.ReplaceAll(rq, c.defaultServiceHost, c.dynamicServiceHost) 233 234 c.requestBody = bytes.NewBufferString(rq) 235 return nil 236 } 237 238 func (c *requestContext) requestHeaderIsSetTo(header, value string) error { 239 c.requestHeaders.Set(header, value) 240 return nil 241 } 242 243 func (c *requestContext) requestJWTTokenIsNotSet() error { 244 c.requestHeaders.Del(headerAuthorization) 245 return nil 246 } 247 248 func (c *requestContext) requestJWTTokenIsValidAdminToken() error { 249 jwtConfig := jwt.NewGuard(c.adminCred) 250 accessToken, err := jwt.IssueAdminToken(jwtConfig.SigningMethod, jwtGo.MapClaims{}, jwtConfig.Timeout) 251 if nil != err { 252 return fmt.Errorf("failed to issue JWT: %v", err) 253 } 254 255 c.requestHeaders.Set(headerAuthorization, "Bearer "+accessToken.Token) 256 257 return nil 258 }