...

Source file src/edge-infra.dev/pkg/edge/api/graph/integration/graphql_client_test.go

Documentation: edge-infra.dev/pkg/edge/api/graph/integration

     1  package integration_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  
    11  	"edge-infra.dev/pkg/edge/api/graph/resolver"
    12  	"edge-infra.dev/pkg/edge/api/middleware"
    13  	"edge-infra.dev/test/framework/integration"
    14  
    15  	"github.com/99designs/gqlgen/client"
    16  	"github.com/mitchellh/mapstructure"
    17  )
    18  
    19  // GraphQlClient represents github.com/99designs/gqlgen/client.Client
    20  type GraphqlClient interface {
    21  	MustPost(query string, response interface{}, options ...client.Option)
    22  	Post(query string, response interface{}, options ...client.Option) error
    23  	RawPost(query string, options ...client.Option) (*client.Response, error)
    24  }
    25  
    26  type BffGraphqlClient struct {
    27  	Endpoint string
    28  	Client   *http.Client
    29  	Options  []client.Option
    30  }
    31  
    32  func NewGraphQlClient(endpoint string, options ...client.Option) *BffGraphqlClient {
    33  	return &BffGraphqlClient{
    34  		Endpoint: endpoint,
    35  		Client:   &http.Client{},
    36  		Options:  options,
    37  	}
    38  }
    39  
    40  func (g *BffGraphqlClient) MustPost(query string, response interface{}, options ...client.Option) {
    41  	if err := g.Post(query, response, options...); err != nil {
    42  		panic(err)
    43  	}
    44  }
    45  
    46  func (g *BffGraphqlClient) Post(query string, response interface{}, options ...client.Option) error {
    47  	respDataRaw, err := g.RawPost(query, options...)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	// we want to unpack even if there is an error, so we can see partial responses
    53  	unpackErr := unpack(respDataRaw.Data, response)
    54  
    55  	if respDataRaw.Errors != nil {
    56  		return client.RawJsonError{RawMessage: respDataRaw.Errors}
    57  	}
    58  	return unpackErr
    59  }
    60  
    61  func (g *BffGraphqlClient) RawPost(query string, options ...client.Option) (*client.Response, error) {
    62  	fmt.Println("Graphql call:", query)
    63  	req, err := g.newRequest(query, options...)
    64  	if err != nil {
    65  		return nil, fmt.Errorf("failed to build http request: %s", err.Error())
    66  	}
    67  
    68  	res, err := g.Client.Do(req)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	body, err := io.ReadAll(res.Body)
    74  	if !validStatus(res.StatusCode) {
    75  		return nil, fmt.Errorf("failed to make graphql request %d: %s. %s", res.StatusCode, res.Status, string(body))
    76  	}
    77  	if err != nil {
    78  		return nil, fmt.Errorf("failed to parse read response body. %w", err)
    79  	}
    80  
    81  	// decode it into map string first, let mapstructure do the final decode
    82  	// because it can be much stricter about unknown fields.
    83  	respDataRaw := &client.Response{}
    84  	if err := json.Unmarshal(body, &respDataRaw); err != nil {
    85  		return nil, fmt.Errorf("failed parse response body to json: %w", err)
    86  	}
    87  	return respDataRaw, nil
    88  }
    89  
    90  // github.com/99designs/gqlgen/client.newRequest
    91  func (g *BffGraphqlClient) newRequest(query string, options ...client.Option) (*http.Request, error) {
    92  	bd := &client.Request{Query: query}
    93  	requestBody, err := json.Marshal(bd)
    94  	if err != nil {
    95  		return nil, fmt.Errorf("failed to encode request body: %w", err)
    96  	}
    97  	if bd.HTTP, err = http.NewRequest(http.MethodPost, g.Endpoint, io.NopCloser(bytes.NewBuffer(requestBody))); err != nil {
    98  		return nil, fmt.Errorf("failed to build new request: %w", err)
    99  	}
   100  	bd.HTTP.Header.Add("Content-Type", "application/json")
   101  	bd.HTTP.Header.Add("Accept", "application/json")
   102  
   103  	// per client options from client.New apply first
   104  	for _, option := range g.Options {
   105  		option(bd)
   106  	}
   107  	// per request options
   108  	for _, option := range options {
   109  		option(bd)
   110  	}
   111  	return bd.HTTP, nil
   112  }
   113  
   114  func unpack(data interface{}, into interface{}) error {
   115  	d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   116  		Result:      into,
   117  		TagName:     "json",
   118  		ErrorUnused: true,
   119  		ZeroFields:  true,
   120  	})
   121  	if err != nil {
   122  		return fmt.Errorf("mapstructure: %s", err.Error())
   123  	}
   124  
   125  	return d.Decode(data)
   126  }
   127  
   128  func validStatus(status int) bool {
   129  	for _, s := range []int{http.StatusOK, http.StatusCreated, http.StatusNoContent} {
   130  		if status == s {
   131  			return true
   132  		}
   133  	}
   134  	return false
   135  }
   136  
   137  // WithFiles encodes the outgoing request body as multipart form data for file variables
   138  func WithAuth(res *resolver.Resolver, org, username, password, role string) client.Option {
   139  	return func(bd *client.Request) {
   140  		if integration.IsIntegrationTest() {
   141  			p, _ := res.UserManagementService.Login(context.Background(), username, password, org)
   142  			bd.HTTP.Header.Set("Authorization", "bearer "+p.Token)
   143  		} else {
   144  			token, _ := middleware.CreateToken(testUser, testEmail, testOrg, jwtSecret, []string{role}, "bslToken", "bsl", "refreshToken")
   145  			bd.HTTP.Header.Set("Authorization", "bearer "+token)
   146  		}
   147  	}
   148  }
   149  

View as plain text