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  }