package integration_test import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "edge-infra.dev/pkg/edge/api/graph/resolver" "edge-infra.dev/pkg/edge/api/middleware" "edge-infra.dev/test/framework/integration" "github.com/99designs/gqlgen/client" "github.com/mitchellh/mapstructure" ) // GraphQlClient represents github.com/99designs/gqlgen/client.Client type GraphqlClient interface { MustPost(query string, response interface{}, options ...client.Option) Post(query string, response interface{}, options ...client.Option) error RawPost(query string, options ...client.Option) (*client.Response, error) } type BffGraphqlClient struct { Endpoint string Client *http.Client Options []client.Option } func NewGraphQlClient(endpoint string, options ...client.Option) *BffGraphqlClient { return &BffGraphqlClient{ Endpoint: endpoint, Client: &http.Client{}, Options: options, } } func (g *BffGraphqlClient) MustPost(query string, response interface{}, options ...client.Option) { if err := g.Post(query, response, options...); err != nil { panic(err) } } func (g *BffGraphqlClient) Post(query string, response interface{}, options ...client.Option) error { respDataRaw, err := g.RawPost(query, options...) if err != nil { return err } // we want to unpack even if there is an error, so we can see partial responses unpackErr := unpack(respDataRaw.Data, response) if respDataRaw.Errors != nil { return client.RawJsonError{RawMessage: respDataRaw.Errors} } return unpackErr } func (g *BffGraphqlClient) RawPost(query string, options ...client.Option) (*client.Response, error) { fmt.Println("Graphql call:", query) req, err := g.newRequest(query, options...) if err != nil { return nil, fmt.Errorf("failed to build http request: %s", err.Error()) } res, err := g.Client.Do(req) if err != nil { return nil, err } body, err := io.ReadAll(res.Body) if !validStatus(res.StatusCode) { return nil, fmt.Errorf("failed to make graphql request %d: %s. %s", res.StatusCode, res.Status, string(body)) } if err != nil { return nil, fmt.Errorf("failed to parse read response body. %w", err) } // decode it into map string first, let mapstructure do the final decode // because it can be much stricter about unknown fields. respDataRaw := &client.Response{} if err := json.Unmarshal(body, &respDataRaw); err != nil { return nil, fmt.Errorf("failed parse response body to json: %w", err) } return respDataRaw, nil } // github.com/99designs/gqlgen/client.newRequest func (g *BffGraphqlClient) newRequest(query string, options ...client.Option) (*http.Request, error) { bd := &client.Request{Query: query} requestBody, err := json.Marshal(bd) if err != nil { return nil, fmt.Errorf("failed to encode request body: %w", err) } if bd.HTTP, err = http.NewRequest(http.MethodPost, g.Endpoint, io.NopCloser(bytes.NewBuffer(requestBody))); err != nil { return nil, fmt.Errorf("failed to build new request: %w", err) } bd.HTTP.Header.Add("Content-Type", "application/json") bd.HTTP.Header.Add("Accept", "application/json") // per client options from client.New apply first for _, option := range g.Options { option(bd) } // per request options for _, option := range options { option(bd) } return bd.HTTP, nil } func unpack(data interface{}, into interface{}) error { d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Result: into, TagName: "json", ErrorUnused: true, ZeroFields: true, }) if err != nil { return fmt.Errorf("mapstructure: %s", err.Error()) } return d.Decode(data) } func validStatus(status int) bool { for _, s := range []int{http.StatusOK, http.StatusCreated, http.StatusNoContent} { if status == s { return true } } return false } // WithFiles encodes the outgoing request body as multipart form data for file variables func WithAuth(res *resolver.Resolver, org, username, password, role string) client.Option { return func(bd *client.Request) { if integration.IsIntegrationTest() { p, _ := res.UserManagementService.Login(context.Background(), username, password, org) bd.HTTP.Header.Set("Authorization", "bearer "+p.Token) } else { token, _ := middleware.CreateToken(testUser, testEmail, testOrg, jwtSecret, []string{role}, "bslToken", "bsl", "refreshToken") bd.HTTP.Header.Set("Authorization", "bearer "+token) } } }