Repository: newrelic/newrelic-client-go
Pull request #187 (2020-03-13 20:34:52)

refactor: Consolidate HTTP Clients
Some refactoring that is in process:
* Renames the `internal/http.NewRelicClient` to `internal/http.Client`
* Consolidates `GraphQLClient` into `Client`, and exposes the `Query` function.
  This simplifies passing around clients for different endpoints, so now as we migrate we could call `Query` instead of `Get` or what not.
* Make all `http.Client` fields private, add a few helper methods

I think this is in a good direction, but not yet there.  Looking for some feedback.


Patch:
diff --git a/internal/http/client.go b/internal/http/client.go
index 4b222d5..09aefbf 100644
--- a/internal/http/client.go
+++ b/internal/http/client.go
@@ -28,22 +28,22 @@ var (
 	defaultUserAgent = fmt.Sprintf("newrelic/%s/%s (https://github.com/newrelic/%s)", defaultServiceName, version.Version, defaultServiceName)
 )
 
-// NewRelicClient represents a client for communicating with the New Relic APIs.
-type NewRelicClient struct {
-	// Client represents the underlying HTTP client.
-	Client *retryablehttp.Client
+// Client represents a client for communicating with the New Relic APIs.
+type Client struct {
+	// client represents the underlying HTTP client.
+	client *retryablehttp.Client
 
-	// Config is the HTTP client configuration.
-	Config config.Config
+	// config is the HTTP client configuration.
+	config config.Config
 
-	// AuthStrategy allows us to use multiple authentication methods for API calls
-	AuthStrategy RequestAuthorizer
+	// authStrategy allows us to use multiple authentication methods for API calls
+	authStrategy RequestAuthorizer
 
 	errorValue ErrorResponse
 }
 
-// NewClient is used to create a new instance of NewRelicClient.
-func NewClient(cfg config.Config) NewRelicClient {
+// NewClient is used to create a new instance of Client.
+func NewClient(cfg config.Config) Client {
 	c := http.Client{
 		Timeout: defaultTimeout,
 	}
@@ -64,6 +64,10 @@ func NewClient(cfg config.Config) NewRelicClient {
 		cfg.BaseURL = region.DefaultBaseURLs[region.Parse(cfg.Region)]
 	}
 
+	if cfg.NerdGraphBaseURL == "" {
+		cfg.NerdGraphBaseURL = region.NerdGraphBaseURLs[region.Parse(cfg.Region)]
+	}
+
 	if cfg.UserAgent == "" {
 		cfg.UserAgent = defaultUserAgent
 	}
@@ -83,16 +87,22 @@ func NewClient(cfg config.Config) NewRelicClient {
 	// Disable logging in go-retryablehttp since we are logging requests directly here
 	r.Logger = nil
 
-	return NewRelicClient{
-		Client:       r,
-		Config:       cfg,
+	return Client{
+		authStrategy: &ClassicV2Authorizer{},
+		client:       r,
+		config:       cfg,
 		errorValue:   &DefaultErrorResponse{},
-		AuthStrategy: &ClassicV2Authorizer{},
 	}
 }
 
+// SetAuthStrategy is used to set the default auth strategy for this client
+// which can be overridden per request
+func (c *Client) SetAuthStrategy(da RequestAuthorizer) {
+	c.authStrategy = da
+}
+
 // SetErrorValue is used to unmarshal error body responses in JSON format.
-func (c *NewRelicClient) SetErrorValue(v ErrorResponse) *NewRelicClient {
+func (c *Client) SetErrorValue(v ErrorResponse) *Client {
 	c.errorValue = v
 	return c
 }
@@ -101,12 +111,12 @@ func (c *NewRelicClient) SetErrorValue(v ErrorResponse) *NewRelicClient {
 // The queryParams argument can be used to add query string parameters to the requested URL.
 // The respBody argument will be unmarshaled from JSON in the response body to the type provided.
 // If respBody is not nil and the response body cannot be unmarshaled to the type provided, an error will be returned.
-func (c *NewRelicClient) Get(
+func (c *Client) Get(
 	url string,
 	queryParams interface{},
 	respBody interface{},
 ) (*http.Response, error) {
-	req, err := c.NewRequest(http.MethodGet, url, queryParams, nil, respBody)
+	req, err := NewRequest(*c, http.MethodGet, url, queryParams, nil, respBody)
 	if err != nil {
 		return nil, err
 	}
@@ -119,13 +129,13 @@ func (c *NewRelicClient) Get(
 // The reqBody argument will be marshaled to JSON from the type provided and included in the request body.
 // The respBody argument will be unmarshaled from JSON in the response body to the type provided.
 // If respBody is not nil and the response body cannot be unmarshaled to the type provided, an error will be returned.
-func (c *NewRelicClient) Post(
+func (c *Client) Post(
 	url string,
 	queryParams interface{},
 	reqBody interface{},
 	respBody interface{},
 ) (*http.Response, error) {
-	req, err := c.NewRequest(http.MethodPost, url, queryParams, reqBody, respBody)
+	req, err := NewRequest(*c, http.MethodPost, url, queryParams, reqBody, respBody)
 	if err != nil {
 		return nil, err
 	}
@@ -135,7 +145,7 @@ func (c *NewRelicClient) Post(
 
 // RawPost behaves the same as Post, but without marshaling the body into JSON before making the request.
 // This is required at least in the case of Synthetics Labels, since the POST doesn't handle JSON.
-func (c *NewRelicClient) RawPost(
+func (c *Client) RawPost(
 	url string,
 	queryParams interface{},
 	reqBody interface{},
@@ -153,7 +163,7 @@ func (c *NewRelicClient) RawPost(
 		return nil, errors.New("invalid request body")
 	}
 
-	req, err := c.NewRequest(http.MethodPost, url, queryParams, requestBody, respBody)
+	req, err := NewRequest(*c, http.MethodPost, url, queryParams, requestBody, respBody)
 	if err != nil {
 		return nil, err
 	}
@@ -166,13 +176,13 @@ func (c *NewRelicClient) RawPost(
 // The reqBody argument will be marshaled to JSON from the type provided and included in the request body.
 // The respBody argument will be unmarshaled from JSON in the response body to the type provided.
 // If respBody is not nil and the response body cannot be unmarshaled to the type provided, an error will be returned.
-func (c *NewRelicClient) Put(
+func (c *Client) Put(
 	url string,
 	queryParams interface{},
 	reqBody interface{},
 	respBody interface{},
 ) (*http.Response, error) {
-	req, err := c.NewRequest(http.MethodPut, url, queryParams, reqBody, respBody)
+	req, err := NewRequest(*c, http.MethodPut, url, queryParams, reqBody, respBody)
 	if err != nil {
 		return nil, err
 	}
@@ -184,11 +194,11 @@ func (c *NewRelicClient) Put(
 // The queryParams argument can be used to add query string parameters to the requested URL.
 // The respBody argument will be unmarshaled from JSON in the response body to the type provided.
 // If respBody is not nil and the response body cannot be unmarshaled to the type provided, an error will be returned.
-func (c *NewRelicClient) Delete(url string,
+func (c *Client) Delete(url string,
 	queryParams interface{},
 	respBody interface{},
 ) (*http.Response, error) {
-	req, err := c.NewRequest(http.MethodDelete, url, queryParams, nil, respBody)
+	req, err := NewRequest(*c, http.MethodDelete, url, queryParams, nil, respBody)
 	if err != nil {
 		return nil, err
 	}
@@ -196,73 +206,23 @@ func (c *NewRelicClient) Delete(url string,
 	return c.Do(req)
 }
 
-// NewRequest creates a new Request struct.
-func (c *NewRelicClient) NewRequest(method string, url string, params interface{}, reqBody interface{}, value interface{}) (*Request, error) {
-	// Make a copy of the client's config
-	cfg := c.Config
-
-	req := &Request{
-		method:       method,
-		url:          url,
-		params:       params,
-		reqBody:      reqBody,
-		value:        value,
-		authStrategy: c.AuthStrategy,
-	}
-
-	req.config = cfg
-
-	u, err := req.makeURL()
-	if err != nil {
-		return nil, err
-	}
-
-	var r *retryablehttp.Request
-	if reqBody != nil {
-		if _, ok := reqBody.([]byte); !ok {
-			reqBody, err = makeRequestBodyReader(reqBody)
-			if err != nil {
-				return nil, err
-			}
-		}
-
-		r, err = retryablehttp.NewRequest(req.method, u.String(), reqBody)
-		if err != nil {
-			return nil, err
-		}
-	} else {
-		r, err = retryablehttp.NewRequest(req.method, u.String(), nil)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	req.request = r
-
-	req.SetHeader(defaultNewRelicRequestingServiceHeader, cfg.ServiceName)
-	req.SetHeader("Content-Type", "application/json")
-	req.SetHeader("User-Agent", cfg.UserAgent)
-
-	return req, nil
-}
-
 // Do initiates an HTTP request as configured by the passed Request struct.
-func (c *NewRelicClient) Do(req *Request) (*http.Response, error) {
+func (c *Client) Do(req *Request) (*http.Response, error) {
 	r, err := req.makeRequest()
 	if err != nil {
 		return nil, err
 	}
 
-	c.Config.GetLogger().Debug("performing request", "method", req.method, "url", r.URL)
+	c.config.GetLogger().Debug("performing request", "method", req.method, "url", r.URL)
 
 	logHeaders, err := json.Marshal(r.Header)
 	if err != nil {
 		return nil, err
 	}
 
-	c.Config.GetLogger().Trace("request details", "headers", string(logHeaders), "body", req.reqBody)
+	c.config.GetLogger().Trace("request details", "headers", string(logHeaders), "body", req.reqBody)
 
-	resp, retryErr := c.Client.Do(r)
+	resp, retryErr := c.client.Do(r)
 	if retryErr != nil {
 		return nil, retryErr
 	}
@@ -283,7 +243,7 @@ func (c *NewRelicClient) Do(req *Request) (*http.Response, error) {
 		return nil, err
 	}
 
-	c.Config.GetLogger().Trace("request completed", "method", req.method, "url", r.URL, "status_code", resp.StatusCode, "headers", string(logHeaders), "body", string(body))
+	c.config.GetLogger().Trace("request completed", "method", req.method, "url", r.URL, "status_code", resp.StatusCode, "headers", string(logHeaders), "body", string(body))
 
 	errorValue := c.errorValue.New()
 	_ = json.Unmarshal(body, &errorValue)
@@ -330,3 +290,35 @@ func makeRequestBodyReader(reqBody interface{}) (*bytes.Buffer, error) {
 
 	return b, nil
 }
+
+// GetBaseURL returns the BaseURL used by the client
+func (c *Client) GetBaseURL() string {
+	return c.config.BaseURL
+}
+
+// Query runs a graphQL query.
+func (c *Client) Query(query string, vars map[string]interface{}, respBody interface{}) error {
+	graphqlReqBody := &graphQLRequest{
+		Query:     query,
+		Variables: vars,
+	}
+
+	graphqlRespBody := &graphQLResponse{
+		Data: respBody,
+	}
+
+	req, err := NewRequest(*c, http.MethodPost, c.config.NerdGraphBaseURL, nil, graphqlReqBody, graphqlRespBody)
+	if err != nil {
+		return err
+	}
+
+	req.SetAuthStrategy(&NerdGraphAuthorizer{})
+	c.SetErrorValue(&graphQLErrorResponse{})
+
+	_, err = c.Do(req)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/internal/http/graphql.go b/internal/http/graphql.go
new file mode 100644
index 0000000..62c980b
--- /dev/null
+++ b/internal/http/graphql.go
@@ -0,0 +1,38 @@
+package http
+
+import (
+	"strings"
+)
+
+type graphQLRequest struct {
+	Query     string                 `json:"query"`
+	Variables map[string]interface{} `json:"variables,omitempty"`
+}
+
+type graphQLResponse struct {
+	Data interface{} `json:"data"`
+}
+
+type graphQLError struct {
+	Message string `json:"message"`
+}
+
+type graphQLErrorResponse struct {
+	Errors []graphQLError `json:"errors"`
+}
+
+func (r *graphQLErrorResponse) Error() string {
+	if len(r.Errors) > 0 {
+		messages := []string{}
+		for _, e := range r.Errors {
+			messages = append(messages, e.Message)
+		}
+		return strings.Join(messages, ", ")
+	}
+
+	return ""
+}
+
+func (r *graphQLErrorResponse) New() ErrorResponse {
+	return &graphQLErrorResponse{}
+}
diff --git a/internal/http/graphql_client.go b/internal/http/graphql_client.go
deleted file mode 100644
index 67cbbb8..0000000
--- a/internal/http/graphql_client.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package http
-
-import (
-	"strings"
-
-	"github.com/newrelic/newrelic-client-go/internal/logging"
-	"github.com/newrelic/newrelic-client-go/internal/region"
-	"github.com/newrelic/newrelic-client-go/pkg/config"
-)
-
-// GraphQLClient represents a graphQL HTTP client.
-type GraphQLClient struct {
-	Client *NewRelicClient
-	config config.Config
-	logger logging.Logger
-}
-
-// NewGraphQLClient returns a new instance of GraphQLClient.
-func NewGraphQLClient(cfg config.Config) *GraphQLClient {
-	if cfg.NerdGraphBaseURL == "" {
-		cfg.NerdGraphBaseURL = region.NerdGraphBaseURLs[region.Parse(cfg.Region)]
-	}
-
-	cfg.BaseURL = cfg.NerdGraphBaseURL
-
-	c := NewClient(cfg)
-	c.AuthStrategy = &NerdGraphAuthorizer{}
-	c.SetErrorValue(&graphQLErrorResponse{})
-
-	return &GraphQLClient{
-		Client: &c,
-		config: cfg,
-		logger: cfg.GetLogger(),
-	}
-}
-
-// Query runs a graphQL query.
-func (g *GraphQLClient) Query(query string, vars map[string]interface{}, respBody interface{}) error {
-	req := graphQLRequest{
-		Query:     query,
-		Variables: vars,
-	}
-
-	resp := graphQLResponse{
-		Data: respBody,
-	}
-
-	if _, err := g.Client.Post(g.config.BaseURL, nil, &req, &resp); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-type graphQLRequest struct {
-	Query     string                 `json:"query"`
-	Variables map[string]interface{} `json:"variables,omitempty"`
-}
-
-type graphQLResponse struct {
-	Data interface{} `json:"data"`
-}
-
-type graphQLError struct {
-	Message string `json:"message"`
-}
-
-type graphQLErrorResponse struct {
-	Errors []graphQLError `json:"errors"`
-}
-
-func (r *graphQLErrorResponse) Error() string {
-	if len(r.Errors) > 0 {
-		messages := []string{}
-		for _, e := range r.Errors {
-			messages = append(messages, e.Message)
-		}
-		return strings.Join(messages, ", ")
-	}
-
-	return ""
-}
-
-func (r *graphQLErrorResponse) New() ErrorResponse {
-	return &graphQLErrorResponse{}
-}
diff --git a/internal/http/request.go b/internal/http/request.go
index 0200fed..fb2c841 100644
--- a/internal/http/request.go
+++ b/internal/http/request.go
@@ -21,6 +21,56 @@ type Request struct {
 	request      *retryablehttp.Request
 }
 
+// NewRequest creates a new Request struct.
+func NewRequest(c Client, method string, url string, params interface{}, reqBody interface{}, value interface{}) (*Request, error) {
+	// Make a copy of the client's config
+	cfg := c.config
+
+	req := &Request{
+		method:       method,
+		url:          url,
+		params:       params,
+		reqBody:      reqBody,
+		value:        value,
+		authStrategy: c.authStrategy,
+	}
+
+	req.config = cfg
+
+	u, err := req.makeURL()
+	if err != nil {
+		return nil, err
+	}
+
+	var r *retryablehttp.Request
+	if reqBody != nil {
+		if _, ok := reqBody.([]byte); !ok {
+			reqBody, err = makeRequestBodyReader(reqBody)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		r, err = retryablehttp.NewRequest(req.method, u.String(), reqBody)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		r, err = retryablehttp.NewRequest(req.method, u.String(), nil)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	req.request = r
+
+	req.SetHeader(defaultNewRelicRequestingServiceHeader, cfg.ServiceName)
+	req.SetHeader("Content-Type", "application/json")
+	req.SetHeader("User-Agent", cfg.UserAgent)
+
+	return req, nil
+}
+
 // SetHeader sets a header on the underlying request.
 func (r *Request) SetHeader(key string, value string) {
 	r.request.Header.Set(key, value)
diff --git a/pkg/alerts/alerts.go b/pkg/alerts/alerts.go
index 532270a..9c46a69 100644
--- a/pkg/alerts/alerts.go
+++ b/pkg/alerts/alerts.go
@@ -10,8 +10,8 @@ import (
 
 // Alerts is used to communicate with New Relic Alerts.
 type Alerts struct {
-	client      http.NewRelicClient
-	infraClient http.NewRelicClient
+	client      http.Client
+	infraClient http.Client
 	logger      logging.Logger
 	pager       http.Pager
 }
@@ -30,7 +30,7 @@ func New(config config.Config) Alerts {
 	infraClient.SetErrorValue(&infrastructure.ErrorResponse{})
 
 	client := http.NewClient(config)
-	client.AuthStrategy = &http.PersonalAPIKeyCapableV2Authorizer{}
+	client.SetAuthStrategy(&http.PersonalAPIKeyCapableV2Authorizer{})
 
 	pkg := Alerts{
 		client:      client,
diff --git a/pkg/apm/apm.go b/pkg/apm/apm.go
index 082dd42..3d97e80 100644
--- a/pkg/apm/apm.go
+++ b/pkg/apm/apm.go
@@ -8,7 +8,7 @@ import (
 
 // APM is used to communicate with the New Relic APM product.
 type APM struct {
-	client http.NewRelicClient
+	client http.Client
 	pager  http.Pager
 }
 
diff --git a/pkg/apm/applications.go b/pkg/apm/applications.go
index 3972985..e0dbed7 100644
--- a/pkg/apm/applications.go
+++ b/pkg/apm/applications.go
@@ -1,8 +1,15 @@
 package apm
 
-import (
-	"fmt"
-)
+// ApplicationsInterface interface should be refactored to be a global interface for fetching NR type things
+type ApplicationsInterface interface {
+	find(accountID int, name string) (*Application, error)
+	list(accountID int, params *ListApplicationsParams) ([]*Application, error)
+
+	create(accountID int, name string) (*Application, error)
+	get(accountID int, applicationID int) (*Application, error)
+	update(accountID int, applicationID int, params UpdateApplicationParams) (*Application, error)
+	remove(accountID int, applicationID int) (*Application, error) // delete is a reserved word...
+}
 
 // Application represents information about a New Relic application.
 type Application struct {
@@ -72,85 +79,42 @@ type UpdateApplicationParams struct {
 
 // ListApplications is used to retrieve New Relic applications.
 func (apm *APM) ListApplications(params *ListApplicationsParams) ([]*Application, error) {
-	apps := []*Application{}
-	nextURL := "/applications.json"
-
-	for nextURL != "" {
-		response := applicationsResponse{}
-		resp, err := apm.client.Get(nextURL, &params, &response)
-
-		if err != nil {
-			return nil, err
-		}
+	accountID := 0
 
-		apps = append(apps, response.Applications...)
-
-		paging := apm.pager.Parse(resp)
-		nextURL = paging.Next
+	method := applicationsREST{
+		parent: apm,
 	}
 
-	return apps, nil
+	return method.list(accountID, params)
 }
 
 // GetApplication is used to retrieve a single New Relic application.
 func (apm *APM) GetApplication(applicationID int) (*Application, error) {
-	response := applicationResponse{}
-	url := fmt.Sprintf("/applications/%d.json", applicationID)
-
-	_, err := apm.client.Get(url, nil, &response)
-
-	if err != nil {
-		return nil, err
+	accountID := 0
+	method := applicationsREST{
+		parent: apm,
 	}
 
-	return &response.Application, nil
+	return method.get(accountID, applicationID)
 }
 
 // UpdateApplication is used to update a New Relic application's name and/or settings.
 func (apm *APM) UpdateApplication(applicationID int, params UpdateApplicationParams) (*Application, error) {
-	response := applicationResponse{}
-	url := fmt.Sprintf("/applications/%d.json", applicationID)
-	reqBody := updateApplicationRequest{
-		Fields: updateApplicationFields(params),
+	accountID := 0
+	method := applicationsREST{
+		parent: apm,
 	}
 
-	_, err := apm.client.Put(url, nil, &reqBody, &response)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return &response.Application, nil
+	return method.update(accountID, applicationID, params)
 }
 
 // DeleteApplication is used to delete a New Relic application.
 // This process will only succeed if the application is no longer reporting data.
 func (apm *APM) DeleteApplication(applicationID int) (*Application, error) {
-	response := applicationResponse{}
-	url := fmt.Sprintf("/applications/%d.json", applicationID)
-
-	_, err := apm.client.Delete(url, nil, &response)
-
-	if err != nil {
-		return nil, err
+	accountID := 0
+	method := applicationsREST{
+		parent: apm,
 	}
 
-	return &response.Application, nil
-}
-
-type applicationsResponse struct {
-	Applications []*Application `json:"applications,omitempty"`
-}
-
-type applicationResponse struct {
-	Application Application `json:"application,omitempty"`
-}
-
-type updateApplicationRequest struct {
-	Fields updateApplicationFields `json:"application"`
-}
-
-type updateApplicationFields struct {
-	Name     string              `json:"name,omitempty"`
-	Settings ApplicationSettings `json:"settings,omitempty"`
+	return method.remove(accountID, applicationID)
 }
diff --git a/pkg/apm/applications_rest.go b/pkg/apm/applications_rest.go
new file mode 100644
index 0000000..e22416d
--- /dev/null
+++ b/pkg/apm/applications_rest.go
@@ -0,0 +1,105 @@
+package apm
+
+import (
+	"fmt"
+)
+
+// applicationsREST implements fetching Applications from the RESTv2 API
+type applicationsREST struct {
+	parent *APM
+}
+
+// list is used to retrieve New Relic applications.
+func (a *applicationsREST) list(accountID int, params *ListApplicationsParams) ([]*Application, error) {
+	apps := []*Application{}
+	nextURL := "/applications.json"
+
+	for nextURL != "" {
+		response := applicationsResponse{}
+		resp, err := a.parent.client.Get(nextURL, &params, &response)
+
+		if err != nil {
+			return nil, err
+		}
+
+		apps = append(apps, response.Applications...)
+
+		paging := a.parent.pager.Parse(resp)
+		nextURL = paging.Next
+	}
+
+	return apps, nil
+}
+
+// find looks for an account / application name combination and returns the result
+func (a *applicationsREST) find(accountID int, name string) (*Application, error) {
+	return nil, fmt.Errorf("find application is not implemented")
+}
+
+// create application is not implemented in the API
+func (a *applicationsREST) create(accountID int, name string) (*Application, error) {
+	return nil, fmt.Errorf("create application is not implemented")
+}
+
+// get is used to retrieve a single New Relic application.
+func (a *applicationsREST) get(accountID int, applicationID int) (*Application, error) {
+	response := applicationResponse{}
+	url := fmt.Sprintf("/applications/%d.json", applicationID)
+
+	_, err := a.parent.client.Get(url, nil, &response)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &response.Application, nil
+}
+
+// update is used to update a New Relic application's name and/or settings.
+func (a *applicationsREST) update(accountID int, applicationID int, params UpdateApplicationParams) (*Application, error) {
+	response := applicationResponse{}
+	url := fmt.Sprintf("/applications/%d.json", applicationID)
+	reqBody := updateApplicationRequest{
+		Fields: updateApplicationFields(params),
+	}
+
+	_, err := a.parent.client.Put(url, nil, &reqBody, &response)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &response.Application, nil
+}
+
+// remove is used to delete a New Relic application.
+// This process will only succeed if the application is no longer reporting data.
+func (a *applicationsREST) remove(accountID int, applicationID int) (*Application, error) {
+	response := applicationResponse{}
+	url := fmt.Sprintf("/applications/%d.json", applicationID)
+
+	_, err := a.parent.client.Delete(url, nil, &response)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &response.Application, nil
+}
+
+type applicationsResponse struct {
+	Applications []*Application `json:"applications,omitempty"`
+}
+
+type applicationResponse struct {
+	Application Application `json:"application,omitempty"`
+}
+
+type updateApplicationRequest struct {
+	Fields updateApplicationFields `json:"application"`
+}
+
+type updateApplicationFields struct {
+	Name     string              `json:"name,omitempty"`
+	Settings ApplicationSettings `json:"settings,omitempty"`
+}
diff --git a/pkg/apm/deployments.go b/pkg/apm/deployments.go
index be2ab15..c97dc33 100644
--- a/pkg/apm/deployments.go
+++ b/pkg/apm/deployments.go
@@ -29,7 +29,7 @@ func (apm *APM) ListDeployments(applicationID int) ([]*Deployment, error) {
 
 	for nextURL != "" {
 		response := deploymentsResponse{}
-		req, err := apm.client.NewRequest("GET", nextURL, nil, nil, &response)
+		req, err := http.NewRequest(apm.client, "GET", nextURL, nil, nil, &response)
 		if err != nil {
 			return nil, err
 		}
@@ -59,7 +59,7 @@ func (apm *APM) CreateDeployment(applicationID int, deployment Deployment) (*Dep
 	resp := deploymentResponse{}
 
 	u := fmt.Sprintf("/applications/%d/deployments.json", applicationID)
-	req, err := apm.client.NewRequest("POST", u, nil, &reqBody, &resp)
+	req, err := http.NewRequest(apm.client, "POST", u, nil, &reqBody, &resp)
 	if err != nil {
 		return nil, err
 	}
@@ -80,7 +80,7 @@ func (apm *APM) DeleteDeployment(applicationID int, deploymentID int) (*Deployme
 	resp := deploymentResponse{}
 	u := fmt.Sprintf("/applications/%d/deployments/%d.json", applicationID, deploymentID)
 
-	req, err := apm.client.NewRequest("DELETE", u, nil, nil, &resp)
+	req, err := http.NewRequest(apm.client, "DELETE", u, nil, nil, &resp)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/dashboards/dashboards.go b/pkg/dashboards/dashboards.go
index 6aa8dc8..d6e24b7 100644
--- a/pkg/dashboards/dashboards.go
+++ b/pkg/dashboards/dashboards.go
@@ -9,7 +9,7 @@ import (
 
 // Dashboards is used to communicate with the New Relic Dashboards product.
 type Dashboards struct {
-	client http.NewRelicClient
+	client http.Client
 	logger logging.Logger
 	pager  http.Pager
 }
diff --git a/pkg/entities/entities.go b/pkg/entities/entities.go
index 9abe5dc..e9b4885 100644
--- a/pkg/entities/entities.go
+++ b/pkg/entities/entities.go
@@ -9,14 +9,14 @@ import (
 
 // Entities is used to communicate with the New Relic Entities product.
 type Entities struct {
-	client *http.GraphQLClient
+	client http.Client
 	logger logging.Logger
 }
 
 // New returns a new client for interacting with New Relic One entities.
 func New(config config.Config) Entities {
 	return Entities{
-		client: http.NewGraphQLClient(config),
+		client: http.NewClient(config),
 		logger: config.GetLogger(),
 	}
 }
diff --git a/pkg/nerdgraph/nerdgraph.go b/pkg/nerdgraph/nerdgraph.go
index d833a8a..70d551e 100644
--- a/pkg/nerdgraph/nerdgraph.go
+++ b/pkg/nerdgraph/nerdgraph.go
@@ -8,7 +8,7 @@ import (
 
 // NerdGraph is used to communicate with the New Relic's GraphQL API, NerdGraph.
 type NerdGraph struct {
-	client *http.GraphQLClient
+	client http.Client
 	logger logging.Logger
 }
 
@@ -23,7 +23,7 @@ type QueryResponse struct {
 // New returns a new GraphQL client for interacting with New Relic's GraphQL API, NerdGraph.
 func New(config config.Config) NerdGraph {
 	return NerdGraph{
-		client: http.NewGraphQLClient(config),
+		client: http.NewClient(config),
 		logger: config.GetLogger(),
 	}
 }
diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go
index 43e88cc..e9436db 100644
--- a/pkg/plugins/plugins.go
+++ b/pkg/plugins/plugins.go
@@ -10,7 +10,7 @@ import (
 
 // Plugins is used to communicate with the New Relic Plugins product.
 type Plugins struct {
-	client http.NewRelicClient
+	client http.Client
 	logger logging.Logger
 	pager  http.Pager
 }
diff --git a/pkg/synthetics/synthetics.go b/pkg/synthetics/synthetics.go
index 447d747..1057d5f 100644
--- a/pkg/synthetics/synthetics.go
+++ b/pkg/synthetics/synthetics.go
@@ -18,7 +18,7 @@ var BaseURLs = map[region.Region]string{
 
 // Synthetics is used to communicate with the New Relic Synthetics product.
 type Synthetics struct {
-	client http.NewRelicClient
+	client http.Client
 	logger logging.Logger
 	pager  http.Pager
 }


Test patch:
diff --git a/internal/http/client_test.go b/internal/http/client_test.go
index af306ac..abed44b 100644
--- a/internal/http/client_test.go
+++ b/internal/http/client_test.go
@@ -35,12 +35,12 @@ func TestConfig(t *testing.T) {
 		UserAgent:      testUserAgent,
 	})
 
-	assert.Equal(t, &testTimeout, c.Config.Timeout)
-	assert.Equal(t, testBaseURL, c.Config.BaseURL)
-	assert.Equal(t, testUserAgent, c.Config.UserAgent)
-	assert.Equal(t, c.Config.ServiceName, testServiceName+"|newrelic-client-go")
+	assert.Equal(t, &testTimeout, c.config.Timeout)
+	assert.Equal(t, testBaseURL, c.config.BaseURL)
+	assert.Equal(t, testUserAgent, c.config.UserAgent)
+	assert.Equal(t, c.config.ServiceName, testServiceName+"|newrelic-client-go")
 
-	assert.Same(t, testTransport, c.Config.HTTPTransport)
+	assert.Same(t, testTransport, c.config.HTTPTransport)
 }
 
 func TestConfigDefaults(t *testing.T) {
@@ -49,9 +49,9 @@ func TestConfigDefaults(t *testing.T) {
 		PersonalAPIKey: testPersonalAPIKey,
 	})
 
-	assert.Equal(t, region.DefaultBaseURLs[region.Parse(c.Config.Region)], c.Config.BaseURL)
-	assert.Contains(t, c.Config.UserAgent, "newrelic/newrelic-client-go/")
-	assert.Equal(t, c.Config.ServiceName, "newrelic-client-go")
+	assert.Equal(t, region.DefaultBaseURLs[region.Parse(c.config.Region)], c.config.BaseURL)
+	assert.Contains(t, c.config.UserAgent, "newrelic/newrelic-client-go/")
+	assert.Equal(t, c.config.ServiceName, "newrelic-client-go")
 }
 
 func TestDefaultErrorValue(t *testing.T) {
@@ -195,7 +195,7 @@ func TestPathOnlyUrl(t *testing.T) {
 		assert.Equal(t, r.URL, "https://www.mocky.io/v2/path")
 	}))
 
-	c.Config.BaseURL = "https://www.mocky.io/v2"
+	c.config.BaseURL = "https://www.mocky.io/v2"
 
 	_, _ = c.Get("/path", nil, nil)
 }
@@ -209,7 +209,7 @@ func TestHostAndPathUrl(t *testing.T) {
 		assert.Equal(t, r.URL, "https:/www.httpbin.org/path")
 	}))
 
-	c.Config.BaseURL = "https://www.mocky.io/v2"
+	c.config.BaseURL = "https://www.mocky.io/v2"
 
 	_, _ = c.Get("https://www.httpbin.org/path", nil, nil)
 }
@@ -286,7 +286,7 @@ func TestCustomRequestHeaders(t *testing.T) {
 		BaseURL:        ts.URL,
 	})
 
-	req, err := c.NewRequest("GET", "/path", nil, nil, nil)
+	req, err := NewRequest(c, "GET", "/path", nil, nil, nil)
 
 	req.SetHeader("user-agent", "custom-user-agent")
 	req.SetServiceName("custom-requesting-service")
diff --git a/internal/http/test_helpers.go b/internal/http/test_helpers.go
index 9cbaf99..205b1a1 100644
--- a/internal/http/test_helpers.go
+++ b/internal/http/test_helpers.go
@@ -13,8 +13,8 @@ var (
 	testUserAgent      = "userAgent"
 )
 
-// NewTestAPIClient returns a test NewRelicClient instance that is configured to communicate with a mock server.
-func NewTestAPIClient(handler http.Handler) NewRelicClient {
+// NewTestAPIClient returns a test Client instance that is configured to communicate with a mock server.
+func NewTestAPIClient(handler http.Handler) Client {
 	ts := httptest.NewServer(handler)
 
 	c := NewClient(config.Config{
diff --git a/pkg/alerts/alerts_test.go b/pkg/alerts/alerts_test.go
index 3e7584a..032a400 100644
--- a/pkg/alerts/alerts_test.go
+++ b/pkg/alerts/alerts_test.go
@@ -8,7 +8,6 @@ import (
 
 	mock "github.com/newrelic/newrelic-client-go/internal/testing"
 	"github.com/newrelic/newrelic-client-go/pkg/config"
-	"github.com/stretchr/testify/assert"
 )
 
 // TODO: This is used by incidents_test.go still, need to refactor
@@ -59,19 +58,3 @@ func newIntegrationTestClient(t *testing.T) Alerts {
 
 	return client
 }
-
-func TestSetBaseURL(t *testing.T) {
-	a := New(config.Config{
-		BaseURL: "http://localhost",
-	})
-
-	assert.Equal(t, "http://localhost", a.client.Config.BaseURL)
-}
-
-func TestSetInfrastructureBaseURL(t *testing.T) {
-	a := New(config.Config{
-		InfrastructureBaseURL: "http://localhost",
-	})
-
-	assert.Equal(t, "http://localhost", a.infraClient.Config.BaseURL)
-}
diff --git a/pkg/entities/entities_test.go b/pkg/entities/entities_test.go
deleted file mode 100644
index 22553c9..0000000
--- a/pkg/entities/entities_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package entities
-
-import (
-	"testing"
-
-	"github.com/newrelic/newrelic-client-go/pkg/config"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestSetNerdGraphBaseURL(t *testing.T) {
-	a := New(config.Config{
-		NerdGraphBaseURL: "http://localhost",
-	})
-
-	assert.Equal(t, "http://localhost", a.client.Client.Config.BaseURL)
-}
diff --git a/pkg/infrastructure/infrastructure_test.go b/pkg/infrastructure/infrastructure_test.go
new file mode 100644
index 0000000..3201de4
--- /dev/null
+++ b/pkg/infrastructure/infrastructure_test.go
@@ -0,0 +1,25 @@
+// +build unit
+
+package infrastructure
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/newrelic/newrelic-client-go/internal/region"
+)
+
+func TestBaseURLs(t *testing.T) {
+	t.Parallel()
+
+	pairs := map[region.Region]string{
+		region.US:      "https://infra-api.newrelic.com/v2",
+		region.EU:      "https://infra-api.eu.newrelic.com/v2",
+		region.Staging: "https://staging-infra-api.newrelic.com/v2",
+	}
+
+	for k, v := range pairs {
+		assert.Equal(t, v, BaseURLs[k])
+	}
+}
diff --git a/pkg/plugins/components_integration_test.go b/pkg/plugins/components_integration_test.go
index 8a8f733..6a8f08c 100644
--- a/pkg/plugins/components_integration_test.go
+++ b/pkg/plugins/components_integration_test.go
@@ -43,6 +43,9 @@ func TestIntegrationComponents(t *testing.T) {
 	require.NoError(t, err)
 	require.NotNil(t, m)
 
+	if len(m) < 1 {
+		t.Skipf("Skipping `GetComponentMetricData` integration test due to zero plugin metrics found")
+	}
 	params := GetComponentMetricDataParams{
 		Names: []string{m[0].Name},
 	}
diff --git a/pkg/synthetics/synthetics_test.go b/pkg/synthetics/synthetics_test.go
index 409a6f0..35b99cd 100644
--- a/pkg/synthetics/synthetics_test.go
+++ b/pkg/synthetics/synthetics_test.go
@@ -25,14 +25,14 @@ func TestBaseURLs(t *testing.T) {
 
 	// Default should be region.US
 	a := New(config.Config{})
-	assert.Equal(t, pairs[region.US], a.client.Config.BaseURL)
+	assert.Equal(t, pairs[region.US], a.client.GetBaseURL())
 
 	for k, v := range pairs {
 		b := New(config.Config{
 			Region: k.String(),
 		})
 
-		assert.Equal(t, v, b.client.Config.BaseURL)
+		assert.Equal(t, v, b.client.GetBaseURL())
 	}
 }
 
@@ -91,11 +91,3 @@ func newMockResponse(
 		require.NoError(t, err)
 	}))
 }
-
-func TestSetSyntheticsBaseURL(t *testing.T) {
-	a := New(config.Config{
-		SyntheticsBaseURL: "http://localhost",
-	})
-
-	assert.Equal(t, "http://localhost", a.client.Config.BaseURL)
-}