Repository: PagerDuty/go-pagerduty
Pull request #325 (2021-04-23 08:45:43)

Add support for better API debugging; start v1.5.0 development
In prepration for #305, this adds a mechanism to inspect all API requests and
responses handled by the client. That way if you want to figure out why
something isn't working as expected, or you'd like to make use of an
undocumented field/feature, you have access to the response and can use it.

This also adds a `Do()` method, with the same signature as `*http.Client.Do()`,
that allows consumrs to generate their own request, have the client add
authentication details and other headers, before sending the request to the
PagerDuty API. This allows consumers to further debug and make use of features
not yet supported in the REST client.

Also, updates Version string to v1.5.0 as this is work towards that release.

Lastly, this updates some of the test files to remove some duplication across
them. These changes were needed due to the new fields added to the struct that
had to be initialized to make HTTP requests.


Patch:
diff --git a/README.md b/README.md
index 0ef8358..d11619f 100644
--- a/README.md
+++ b/README.md
@@ -42,9 +42,22 @@ An example of the `service` sub-command
 pd service list
 ```
 
-
 ### Client Library
 
+#### NOTICE: Breaking API Changes in master branch
+
+As part of the upcoming `v1.5.0` release, we will be fixing features that have
+never worked correctly and require a breaking API change to fix. One example is
+the issue reported in [\#232](https://github.com/PagerDuty/go-pagerduty/issues/232),
+as well as a handful of other examples within the
+[v1.5.0 milestone](https://github.com/PagerDuty/go-pagerduty/milestone/2).
+
+As a result, the `master` branch now contains breaking changes since the last
+`v1.4.x` release. We will clearly highlight the breaking changes in the `v1.5.0`
+release notes when it's ready.
+
+#### Example Usage
+
 ```go
 package main
 
@@ -113,6 +126,53 @@ func main() {
 }
 ```
 
+#### Extending and Debugging Client
+
+##### Extending The Client
+
+The `*pagerduty.Client` has a `Do` method which allows consumers to wrap the
+client, and make their own requests to the PagerDuty API. The method signature
+is similar to that of the `http.Client.Do` method, except it also includes a
+`bool` to incidate whether the API endpoint is authenticated (i.e., the REST
+API). When the API is authenticated, the client will annotate the request with
+the appropriate headers to be authenticated by the API.
+
+If PagerDuty the client doesn't natively expose functionality that you wish to
+use, such as undocumented JSON fields, you can use the `Do()` method to issue
+your own request that you can parse the response of.
+
+Likewise, you can use it to issue requests to the API for the purposes of
+debugging. However, that's not the only mechanism for debugging.
+
+##### Debugging the Client
+
+The `*pagerduty.Client` has a method that allows consumers to enable debug
+functionality, including interception of PagerDuty API responses. This is done
+by using the `SetDebugFlag()` method using the `pagerduty.DebugFlag` unsigned
+integer type. There are also exported constants to help consumers enable
+specific debug behaviors.
+
+###### Capturing Last PagerDuty Response
+
+If you're not getting the response you expect from the PagerDuty Go client, you
+can enable the `DebugCaptureLastResponse` debug flag to capture the HTTP
+responses. You can then use one of the methods to make an API call, and then
+inspect the API response received. For example:
+
+```Go
+client := pagerduty.NewClient("exmaple")
+
+client.SetDebugFlag(pagerduty.DebugCaptureLastResponse)
+
+oncalls, err := client.ListOnCallsWithContext(ctx, pagerduty.ListOnCallOptions{})
+
+resp, ok := client.LastAPIReponse()
+if ok { // resp is an *http.Response we can inspect
+	body, err := ioutil.ReadAll(resp.Body)
+    // ...
+}
+```
+
 ## Contributing
 
 1. Fork it ( https://github.com/PagerDuty/go-pagerduty/fork )
diff --git a/client.go b/client.go
index caf042b..a57b9bc 100644
--- a/client.go
+++ b/client.go
@@ -1,3 +1,11 @@
+// Package pagerduty is a Go API client for both the PagerDuty v2 REST and
+// Events API. Most methods should be implemented, and it's recommended to use
+// the WithContext variant of each method and to specify a context with a
+// timeout.
+//
+// To debug responses from the API, you can instruct the client to capture the
+// last response from the API. Please see the documentation for the
+// SetDebugFlag() and LastAPIResponse() methods for more details.
 package pagerduty
 
 import (
@@ -6,14 +14,19 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"net"
 	"net/http"
 	"path"
 	"runtime"
 	"strings"
+	"sync/atomic"
 	"time"
 )
 
+// Version is current version of this client.
+const Version = "1.5.0"
+
 const (
 	apiEndpoint         = "https://api.pagerduty.com"
 	v2EventsAPIEndpoint = "https://events.pagerduty.com"
@@ -194,6 +207,10 @@ var defaultHTTPClient HTTPClient = newDefaultHTTPClient()
 
 // Client wraps http client
 type Client struct {
+	debugFlag    *uint64
+	lastRequest  *atomic.Value
+	lastResponse *atomic.Value
+
 	authToken           string
 	apiEndpoint         string
 	v2EventsAPIEndpoint string
@@ -210,6 +227,9 @@ type Client struct {
 // NewClient creates an API client using an account/user API token
 func NewClient(authToken string, options ...ClientOptions) *Client {
 	client := Client{
+		debugFlag:           new(uint64),
+		lastRequest:         &atomic.Value{},
+		lastResponse:        &atomic.Value{},
 		authToken:           authToken,
 		apiEndpoint:         apiEndpoint,
 		v2EventsAPIEndpoint: v2EventsAPIEndpoint,
@@ -253,6 +273,119 @@ func WithOAuth() ClientOptions {
 	}
 }
 
+// DebugFlag represents a set of debug bit flags that can be bitwise-ORed
+// together to configure the different behaviors. This allows us to expand
+// functionality in the future without introducing breaking changes.
+type DebugFlag uint64
+
+const (
+	// DebugDisabled disables all debug behaviors.
+	DebugDisabled DebugFlag = 0
+
+	// DebugCaptureLastRequest captures the last HTTP request made to the API
+	// (if there was one) and makes it available via the LastAPIRequest()
+	// method.
+	//
+	// This may increase memory usage / GC, as we'll be making a copy of the
+	// full HTTP request body on each request and capturing it for inspection.
+	DebugCaptureLastRequest DebugFlag = 1 << 0
+
+	// DebugCaptureLastResponse captures the last HTTP response from the API (if
+	// there was one) and makes it available via the LastAPIReponse() method.
+	//
+	// This may increase memory usage / GC, as we'll be making a copy of the
+	// full HTTP response body on each request and capturing it for inspection.
+	DebugCaptureLastResponse DebugFlag = 1 << 1
+)
+
+// SetDebugFlag sets the DebugFlag of the client, which are just bit flags that
+// tell the client how to behave. They can be bitwise-ORed together to enable
+// multiple behaviors.
+func (c *Client) SetDebugFlag(flag DebugFlag) {
+	atomic.StoreUint64(c.debugFlag, uint64(flag))
+}
+
+func (c *Client) debugCaptureRequest() bool {
+	return atomic.LoadUint64(c.debugFlag)&uint64(DebugCaptureLastRequest) > 0
+}
+
+func (c *Client) debugCaptureResponse() bool {
+	return atomic.LoadUint64(c.debugFlag)&uint64(DebugCaptureLastResponse) > 0
+}
+
+// LastAPIRequest returns the last request sent to the API, if enabled. This can
+// be turned on by using the SetDebugFlag() method while providing the
+// DebugCaptureLastRequest flag.
+//
+// The bool value returned from this method is false if the request is unset or
+// is nil. If there was an error prepping the request to be sent to the server,
+// there will be no *http.Request to capture so this will return (<nil>, false).
+//
+// This is meant to help with debugging unexpected API interactions, so most
+// won't need to use it. Also, callers will need to ensure the *Client isn't
+// used concurrently, otherwise they might receive another method's *http.Request
+// and not the one they anticipated.
+//
+// The *http.Request made within the Do() method is not captured by the client,
+// and thus won't be returned by this method.
+func (c *Client) LastAPIRequest() (*http.Request, bool) {
+	v := c.lastRequest.Load()
+	if v == nil {
+		return nil, false
+	}
+
+	// comma ok idiom omitted, if this is something else explode
+	r := v.(*http.Request)
+	if r == nil {
+		return nil, false
+	}
+
+	return r, true
+}
+
+// LastAPIResponse returns the last response received from the API, if enabled.
+// This can be turned on by using the SetDebugFlag() method while providing the
+// DebugCaptureLastResponse flag.
+//
+// The bool value returned from this method is false if the response is unset or
+// is nil. If the HTTP exchange failed (e.g., there was a connection error)
+// there will be no *http.Response to capture so this will return (<nil>,
+// false).
+//
+// This is meant to help with debugging unexpected API interactions, so most
+// won't need to use it. Also, callers will need to ensure the *Client isn't
+// used concurrently, otherwise they might receive another method's *http.Response
+// and not the one they anticipated.
+//
+// The *http.Response from the Do() method is not captured by the client, and thus
+// won't be returned by this method.
+func (c *Client) LastAPIResponse() (*http.Response, bool) {
+	v := c.lastResponse.Load()
+	if v == nil {
+		return nil, false
+	}
+
+	// comma ok idiom omitted, if this is something else explode
+	r := v.(*http.Response)
+	if r == nil {
+		return nil, false
+	}
+
+	return r, true
+}
+
+// Do sets some headers on the request, before actioning it using the internal
+// HTTPClient. If the PagerDuty API you're communicating with requires
+// authentication, such as the REST API, set authRequired to true and the client
+// will set the proper authentication headers onto the request. This also
+// assumes any request body is in JSON format and sets the Content-Type to
+// application/json.
+func (c *Client) Do(r *http.Request, authRequired bool) (*http.Response, error) {
+	c.prepRequest(r, authRequired, nil)
+
+	return c.HTTPClient.Do(r)
+}
+
 func (c *Client) delete(ctx context.Context, path string) (*http.Response, error) {
 	return c.do(ctx, http.MethodDelete, path, nil, nil)
 }
@@ -280,14 +413,14 @@ func (c *Client) get(ctx context.Context, path string) (*http.Response, error) {
 	return c.do(ctx, http.MethodGet, path, nil, nil)
 }
 
-// needed where pagerduty use a different endpoint for certain actions (eg: v2 events)
-func (c *Client) doWithEndpoint(ctx context.Context, endpoint, method, path string, authRequired bool, body io.Reader, headers map[string]string) (*http.Response, error) {
-	req, err := http.NewRequestWithContext(ctx, method, endpoint+path, body)
-	if err != nil {
-		return nil, fmt.Errorf("failed to build request: %w", err)
-	}
+const (
+	userAgentHeader   = "go-pagerduty/" + Version
+	acceptHeader      = "application/vnd.pagerduty+json;version=2"
+	contentTypeHeader = "application/json"
+)
 
-	req.Header.Set("Accept", "application/vnd.pagerduty+json;version=2")
+func (c *Client) prepRequest(req *http.Request, authRequired bool, headers map[string]string) {
+	req.Header.Set("Accept", acceptHeader)
 
 	for k, v := range headers {
 		req.Header.Set(k, v)
@@ -302,10 +435,62 @@ func (c *Client) doWithEndpoint(ctx context.Context, endpoint, method, path stri
 		}
 	}
 
-	req.Header.Set("User-Agent", "go-pagerduty/"+Version)
-	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("User-Agent", userAgentHeader)
+	req.Header.Set("Content-Type", contentTypeHeader)
+}
+
+func dupeRequest(r *http.Request) (*http.Request, error) {
+	data, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to copy request body: %w", err)
+	}
+
+	_ = r.Body.Close()
+
+	dreq := r.Clone(r.Context())
+
+	r.Body = ioutil.NopCloser(bytes.NewReader(data))
+	dreq.Body = ioutil.NopCloser(bytes.NewReader(data))
+
+	return dreq, nil
+}
+
+// needed where pagerduty use a different endpoint for certain actions (eg: v2 events)
+func (c *Client) doWithEndpoint(ctx context.Context, endpoint, method, path string, authRequired bool, body io.Reader, headers map[string]string) (*http.Response, error) {
+	var dreq *http.Request
+	var resp *http.Response
+
+	// so that the last request and response can be nil if there was an error
+	// before the request could be fully processed by the origin, we defer these
+	// calls here
+	if c.debugCaptureResponse() {
+		defer func() {
+			c.lastResponse.Store(resp)
+		}()
+	}
+
+	if c.debugCaptureRequest() {
+		defer func() {
+			c.lastRequest.Store(dreq)
+		}()
+	}
+
+	req, err := http.NewRequestWithContext(ctx, method, endpoint+path, body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to build request: %w", err)
+	}
+
+	c.prepRequest(req, authRequired, headers)
+
+	// if in debug mode, copy request before making it
+	if c.debugCaptureRequest() {
+		if dreq, err = dupeRequest(req); err != nil {
+			return nil, fmt.Errorf("failed to duplicate request for debug capture: %w", err)
+		}
+	}
+
+	resp, err = c.HTTPClient.Do(req)
 
-	resp, err := c.HTTPClient.Do(req)
 	return c.checkResponse(resp, err)
 }
 
@@ -314,10 +499,21 @@ func (c *Client) do(ctx context.Context, method, path string, body io.Reader, he
 }
 
 func (c *Client) decodeJSON(resp *http.Response, payload interface{}) error {
-	defer func() { _ = resp.Body.Close() }() // explicitly discard error
+	// close the original response body, and not the copy we may make if
+	// debugCaptureResponse is true
+	orb := resp.Body
+	defer func() { _ = orb.Close() }() // explicitly discard error
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return fmt.Errorf("failed to read response body: %w", err)
+	}
+
+	if c.debugCaptureResponse() { // reset body as we capture the response elsewhere
+		resp.Body = ioutil.NopCloser(bytes.NewReader(body))
+	}
 
-	decoder := json.NewDecoder(resp.Body)
-	return decoder.Decode(payload)
+	return json.Unmarshal(body, payload)
 }
 
 func (c *Client) checkResponse(resp *http.Response, err error) (*http.Response, error) {
diff --git a/constants.go b/constants.go
deleted file mode 100644
index 3dfc8b5..0000000
--- a/constants.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package pagerduty
-
-const (
-	// Version is current version of this client.
-	Version = "1.4.1"
-)


Test patch:
diff --git a/ability_test.go b/ability_test.go
index 4841d2f..ed13006 100644
--- a/ability_test.go
+++ b/ability_test.go
@@ -15,7 +15,7 @@ func TestAbility_ListAbilities(t *testing.T) {
 		_, _ = w.Write([]byte(`{"abilities": ["sso"]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	want := &ListAbilityResponse{Abilities: []string{"sso"}}
 
 	res, err := client.ListAbilities()
@@ -34,7 +34,7 @@ func TestAbility_ListAbilitiesFailure(t *testing.T) {
 		w.WriteHeader(http.StatusForbidden)
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	if _, err := client.ListAbilities(); err == nil {
 		t.Fatal("expected error; got nil")
@@ -50,7 +50,7 @@ func TestAbility_TestAbility(t *testing.T) {
 		w.WriteHeader(http.StatusNoContent)
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	if err := client.TestAbility("sso"); err != nil {
 		t.Fatal(err)
@@ -66,7 +66,7 @@ func TestAbility_TestAbilityFailure(t *testing.T) {
 		w.WriteHeader(http.StatusForbidden)
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	if err := client.TestAbility("sso"); err == nil {
 		t.Fatal("expected error; got nil")
diff --git a/addon_test.go b/addon_test.go
index 7c0dc87..30fa22c 100644
--- a/addon_test.go
+++ b/addon_test.go
@@ -15,7 +15,7 @@ func TestAddon_List(t *testing.T) {
 	})
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
 	var opts ListAddonOptions
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.ListAddons(opts)
 	want := &ListAddonResponse{
@@ -46,7 +46,7 @@ func TestAddon_Install(t *testing.T) {
 		_, _ = w.Write([]byte(`{"addon": {"name": "Internal Status Page", "id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.InstallAddon(input)
 
@@ -70,7 +70,7 @@ func TestAddon_Get(t *testing.T) {
 		testMethod(t, r, "GET")
 		_, _ = w.Write([]byte(`{"addon": {"id": "1"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.GetAddon("1")
 
@@ -93,7 +93,7 @@ func TestAddon_Update(t *testing.T) {
 		testMethod(t, r, "PUT")
 		_, _ = w.Write([]byte(`{"addon": {"name": "Internal Status Page", "id": "1"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	input := Addon{
 		Name: "Internal Status Page",
@@ -121,7 +121,7 @@ func TestAddon_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 		w.WriteHeader(http.StatusNoContent)
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	err := client.DeleteAddon("1")
 	if err != nil {
 		t.Fatal(err)
diff --git a/analytics_test.go b/analytics_test.go
index baa7c87..07c3fb5 100644
--- a/analytics_test.go
+++ b/analytics_test.go
@@ -37,11 +37,7 @@ func TestAnalytics_GetAggregatedIncidentData(t *testing.T) {
 		_, _ = w.Write(bytesAnalyticsResponse)
 	})
 
-	client := &Client{
-		apiEndpoint: server.URL,
-		authToken:   "foo",
-		HTTPClient:  defaultHTTPClient,
-	}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.GetAggregatedIncidentData(context.Background(), analyticsRequest)
 	want := AnalyticsResponse{
@@ -85,11 +81,7 @@ func TestAnalytics_GetAggregatedServiceData(t *testing.T) {
 		_, _ = w.Write(bytesAnalyticsResponse)
 	})
 
-	client := &Client{
-		apiEndpoint: server.URL,
-		authToken:   "foo",
-		HTTPClient:  defaultHTTPClient,
-	}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.GetAggregatedServiceData(context.Background(), analyticsRequest)
 	want := AnalyticsResponse{
@@ -133,11 +125,7 @@ func TestAnalytics_GetAggregatedTeamData(t *testing.T) {
 		_, _ = w.Write(bytesAnalyticsResponse)
 	})
 
-	client := &Client{
-		apiEndpoint: server.URL,
-		authToken:   "foo",
-		HTTPClient:  defaultHTTPClient,
-	}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.GetAggregatedTeamData(context.Background(), analyticsRequest)
 	want := AnalyticsResponse{
diff --git a/business_service_test.go b/business_service_test.go
index 87c0d27..b05ca35 100644
--- a/business_service_test.go
+++ b/business_service_test.go
@@ -18,7 +18,7 @@ func TestBusinessService_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListBusinessServiceOptions{
 		APIListObject: listObj,
 	}
@@ -47,7 +47,7 @@ func TestBusinessService_Create(t *testing.T) {
 		_, _ = w.Write([]byte(`{"business_service": {"id": "1", "name": "foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &BusinessService{
 		Name: "foo",
 	}
@@ -74,7 +74,7 @@ func TestBusinessService_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"business_service": {"id": "1", "name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ruleSetID := "1"
 
 	res, _, err := client.GetBusinessService(ruleSetID)
@@ -112,7 +112,7 @@ func TestBusinessService_Update(t *testing.T) {
 		_, _ = w.Write([]byte(`{"business_service": {"id": "1", "name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &BusinessService{
 		ID:   "1",
 		Name: "foo",
@@ -139,7 +139,7 @@ func TestBusinessService_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ID := "1"
 	err := client.DeleteBusinessService(ID)
 	if err != nil {
diff --git a/change_events_test.go b/change_events_test.go
index 9a6dd9b..f197f6c 100644
--- a/change_events_test.go
+++ b/change_events_test.go
@@ -26,12 +26,7 @@ func TestChangeEvent_Create(t *testing.T) {
 		},
 	)
 
-	client := &Client{
-		v2EventsAPIEndpoint: server.URL,
-		apiEndpoint:         server.URL,
-		authToken:           "foo",
-		HTTPClient:          defaultHTTPClient,
-	}
+	client := defaultTestClient(server.URL, "foo")
 
 	want := ChangeEventResponse{
 		Status:  "success",
@@ -81,12 +76,7 @@ func TestChangeEvent_CreateWithPayloadVerification(t *testing.T) {
 		},
 	)
 
-	client := &Client{
-		v2EventsAPIEndpoint: server.URL,
-		apiEndpoint:         server.URL,
-		authToken:           "foo",
-		HTTPClient:          defaultHTTPClient,
-	}
+	client := defaultTestClient(server.URL, "foo")
 
 	eventDetails := map[string]interface{}{"DetailKey1": "DetailValue1", "DetailKey2": "DetailValue2"}
 	ce := ChangeEvent{
diff --git a/client_test.go b/client_test.go
index 6994ff1..8f02fa3 100644
--- a/client_test.go
+++ b/client_test.go
@@ -1,12 +1,16 @@
 package pagerduty
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
+	"io"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"reflect"
 	"strings"
+	"sync/atomic"
 	"testing"
 )
 
@@ -32,7 +36,21 @@ func teardown() {
 	server.Close()
 }
 
+func defaultTestClient(serverURL, authToken string) *Client {
+	return &Client{
+		v2EventsAPIEndpoint: serverURL,
+		apiEndpoint:         serverURL,
+		authToken:           authToken,
+		HTTPClient:          defaultHTTPClient,
+		debugFlag:           new(uint64),
+		lastRequest:         &atomic.Value{},
+		lastResponse:        &atomic.Value{},
+	}
+}
+
 func testMethod(t *testing.T, r *http.Request, want string) {
+	t.Helper()
+
 	if got := r.Method; got != want {
 		t.Errorf("Request method: %v, want %v", got, want)
 	}
@@ -332,3 +350,307 @@ func TestAPIError_NotFound(t *testing.T) {
 		})
 	}
 }
+
+func TestClient_SetDebugFlag(t *testing.T) {
+	c := defaultTestClient("", "")
+	c.SetDebugFlag(42)
+
+	tests := []struct {
+		name string
+		flag DebugFlag
+	}{
+		{
+			name: "zero_flag",
+		},
+
+		{
+			name: "capture_response_flag",
+			flag: DebugCaptureLastResponse,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c.SetDebugFlag(tt.flag)
+
+			got := atomic.LoadUint64(c.debugFlag)
+			if got != uint64(tt.flag) {
+				t.Fatalf("got = %64b, want = %64b", got, tt.flag)
+			}
+		})
+	}
+}
+
+func TestClient_LastAPIRequest(t *testing.T) {
+	t.Run("unit", func(t *testing.T) {
+		c := defaultTestClient("", "")
+		got, ok := c.LastAPIRequest()
+		if ok {
+			t.Fatal("new client ok = true, want false")
+		}
+
+		if got != nil {
+			t.Fatal("got != nil")
+		}
+
+		tests := []struct {
+			name string
+			req  *http.Request
+			ok   bool
+		}{
+			{
+				name: "nil_response",
+			},
+			{
+				name: "non-nil_response",
+				req:  &http.Request{},
+				ok:   true,
+			},
+		}
+
+		for _, tt := range tests {
+			t.Run(tt.name, func(t *testing.T) {
+				c.lastRequest.Store(tt.req)
+
+				got, ok = c.LastAPIRequest()
+				if ok != tt.ok {
+					t.Fatalf("ok = %t, want %t", ok, tt.ok)
+				}
+
+				if !ok {
+					if got != nil {
+						t.Fatal("got != nil")
+					}
+					return
+				}
+
+				if got != tt.req {
+					t.Fatalf("got = %v, want %v", got, tt.req)
+				}
+			})
+		}
+	})
+
+	t.Run("integration", func(t *testing.T) {
+		const requestBody = `{"user":{"id":"1","type":"","name":"","summary":"","email":"foo@bar.com","contact_methods":null,"notification_rules":null,"Teams":null}}`
+
+		setup()
+		defer teardown()
+
+		mux.HandleFunc("/users/1", func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, http.MethodPut)
+			_, _ = w.Write([]byte(`{"user": {"id": "1", "email":"foo@bar.com"}}`))
+		})
+
+		c := defaultTestClient(server.URL, "foo")
+		c.SetDebugFlag(DebugCaptureLastRequest)
+
+		_, err := c.UpdateUserWithContext(context.Background(), User{
+			APIObject: APIObject{
+				ID: "1",
+			},
+			Email: "foo@bar.com",
+		})
+		testErrCheck(t, "c.UpdateUserWithContext()", "", err)
+
+		got, ok := c.LastAPIRequest()
+		if !ok {
+			t.Fatal("ok = false, want true")
+		}
+
+		if got == nil {
+			t.Fatal("got == nil")
+		}
+
+		if got.Method != http.MethodPut {
+			t.Fatalf("got.Method = %s, want %s", got.Method, http.MethodPut)
+		}
+
+		body, err := ioutil.ReadAll(got.Body)
+		testErrCheck(t, "ioutil.ReadAll()", "", err)
+
+		if jb := string(body); jb != requestBody {
+			t.Fatalf("got.Body = %q, want %q", jb, requestBody)
+		}
+	})
+}
+
+func TestClient_LastAPIResponse(t *testing.T) {
+	t.Run("unit", func(t *testing.T) {
+		c := defaultTestClient("", "")
+		got, ok := c.LastAPIResponse()
+		if ok {
+			t.Fatal("new client ok = true, want false")
+		}
+
+		if got != nil {
+			t.Fatal("got != nil")
+		}
+
+		tests := []struct {
+			name string
+			resp *http.Response
+			ok   bool
+		}{
+			{
+				name: "nil_response",
+			},
+			{
+				name: "non-nil_response",
+				resp: &http.Response{},
+				ok:   true,
+			},
+		}
+
+		for _, tt := range tests {
+			t.Run(tt.name, func(t *testing.T) {
+				c.lastResponse.Store(tt.resp)
+
+				got, ok = c.LastAPIResponse()
+				if ok != tt.ok {
+					t.Fatalf("ok = %t, want %t", ok, tt.ok)
+				}
+
+				if !ok {
+					if got != nil {
+						t.Fatal("got != nil")
+					}
+					return
+				}
+
+				if got != tt.resp {
+					t.Fatalf("got = %v, want %v", got, tt.resp)
+				}
+			})
+		}
+	})
+
+	t.Run("integration", func(t *testing.T) {
+		const responseBody = `{"user": {"id": "1", "email":"foo@bar.com"}}`
+
+		setup()
+		defer teardown()
+
+		mux.HandleFunc("/users/1", func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, http.MethodGet)
+			_, _ = w.Write([]byte(responseBody))
+		})
+
+		c := defaultTestClient(server.URL, "foo")
+		c.SetDebugFlag(DebugCaptureLastResponse)
+
+		_, err := c.GetUser("1", GetUserOptions{})
+		testErrCheck(t, "c.GetUser()", "", err)
+
+		got, ok := c.LastAPIResponse()
+		if !ok {
+			t.Fatal("ok = false, want true")
+		}
+
+		if got == nil {
+			t.Fatal("got == nil")
+		}
+
+		if got.StatusCode != 200 {
+			t.Errorf("got.StatusCode = %d, want 200", got.StatusCode)
+		}
+
+		body, err := ioutil.ReadAll(got.Body)
+		testErrCheck(t, "ioutil.ReadAll()", "", err)
+
+		if jb := string(body); jb != responseBody {
+			t.Fatalf("got.Body = %q, want %q", jb, responseBody)
+		}
+	})
+}
+
+func clientDoHandler(t *testing.T, needsAuth bool) func(w http.ResponseWriter, r *http.Request) {
+	t.Helper()
+
+	return func(w http.ResponseWriter, r *http.Request) {
+		t.Helper()
+		testMethod(t, r, http.MethodPost)
+
+		auth := r.Header.Get("Authorization")
+		if needsAuth && auth != "Token token=foo" {
+			_, _ = w.Write([]byte("badAuth"))
+			w.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+
+		if !needsAuth && len(auth) > 0 {
+			_, _ = w.Write([]byte("Authentication header should not be provided"))
+			w.WriteHeader(http.StatusBadRequest)
+			return
+		}
+
+		if accept := r.Header.Get("Accept"); accept != acceptHeader {
+			_, _ = w.Write([]byte(fmt.Sprintf("%q Accept unexpected", accept)))
+			w.WriteHeader(http.StatusNotAcceptable)
+			return
+		}
+
+		if ua := r.Header.Get("User-Agent"); ua != userAgentHeader {
+			_, _ = w.Write([]byte(fmt.Sprintf("%q User-Agent unexpected", ua)))
+			w.WriteHeader(http.StatusBadRequest)
+			return
+		}
+
+		if ct := r.Header.Get("Content-Type"); ct != contentTypeHeader {
+			_, _ = w.Write([]byte(fmt.Sprintf("%q Content-Type unexpected", ct)))
+			w.WriteHeader(http.StatusUnsupportedMediaType)
+			return
+		}
+
+		_, _ = w.Write([]byte("ok"))
+	}
+}
+
+func TestClient_Do(t *testing.T) {
+	c := defaultTestClient(server.URL, "foo")
+
+	tests := []struct {
+		name string
+		auth bool
+	}{
+		{
+			name: "no_auth",
+			auth: false,
+		},
+		{
+			name: "auth",
+			auth: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			setup()
+			defer teardown()
+
+			mux.HandleFunc("/test", clientDoHandler(t, tt.auth))
+
+			req, err := http.NewRequest(http.MethodPost, server.URL+"/test", strings.NewReader(`{"empty":"object"}`))
+			testErrCheck(t, "http.NewRequest()", "", err)
+
+			resp, err := c.Do(req, tt.auth)
+			testErrCheck(t, "c.Do()", "", err)
+
+			defer func() {
+				_, _ = io.Copy(ioutil.Discard, resp.Body)
+				_ = resp.Body.Close()
+			}()
+
+			body, err := ioutil.ReadAll(resp.Body)
+			testErrCheck(t, "ioutil.ReadAll()", "", err)
+
+			if resp.StatusCode != 200 {
+				t.Fatalf("request failed with status %q: %s", resp.Status, string(body))
+			}
+
+			if bs := string(body); bs != "ok" {
+				t.Fatalf("body = %s, want ok", bs)
+			}
+		})
+	}
+}
diff --git a/escalation_policy_test.go b/escalation_policy_test.go
index 8ab6aa6..72bee1d 100644
--- a/escalation_policy_test.go
+++ b/escalation_policy_test.go
@@ -16,7 +16,7 @@ func TestEscalationPolicy_List(t *testing.T) {
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
 	var opts ListEscalationPoliciesOptions
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.ListEscalationPolicies(opts)
 
@@ -46,7 +46,7 @@ func TestEscalationPolicy_Create(t *testing.T) {
 		testMethod(t, r, "POST")
 		_, _ = w.Write([]byte(`{"escalation_policy": {"name": "foo", "id": "1"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	res, err := client.CreateEscalationPolicy(input)
 
 	want := &EscalationPolicy{
@@ -70,7 +70,7 @@ func TestEscalationPolicy_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 		w.WriteHeader(http.StatusNoContent)
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	err := client.DeleteEscalationPolicy("1")
 	if err != nil {
 		t.Fatal(err)
@@ -85,7 +85,7 @@ func TestEscalationPolicy_Get(t *testing.T) {
 		testMethod(t, r, "GET")
 		_, _ = w.Write([]byte(`{"escalation_policy": {"id": "1"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	var opts *GetEscalationPolicyOptions
 	res, err := client.GetEscalationPolicy("1", opts)
 
@@ -110,7 +110,7 @@ func TestEscalationPolicy_Update(t *testing.T) {
 		_, _ = w.Write([]byte(`{"escalation_policy": {"name": "foo", "id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &EscalationPolicy{Name: "foo"}
 	want := &EscalationPolicy{
 		APIObject: APIObject{
@@ -142,7 +142,7 @@ func TestEscalationPolicy_UpdateTeams(t *testing.T) {
 		_, _ = w.Write([]byte(`{"escalation_policy": {"name": "foo", "id": "1", "teams": []}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	res, err := client.UpdateEscalationPolicy("1", input)
 
 	want := &EscalationPolicy{
diff --git a/event_v2_test.go b/event_v2_test.go
index 2779a7d..d590959 100644
--- a/event_v2_test.go
+++ b/event_v2_test.go
@@ -13,18 +13,22 @@ func TestEventV2_ManageEvent(t *testing.T) {
 		testMethod(t, r, "POST")
 		_, _ = w.Write([]byte(`{"status": "ok", "dedup_key": "yes", "message": "ok"}`))
 	})
-	client := &Client{v2EventsAPIEndpoint: server.URL, apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+
+	client := defaultTestClient(server.URL, "foo")
 	evt := &V2Event{
 		RoutingKey: "abc123",
 	}
+
 	res, err := client.ManageEvent(evt)
 	if err != nil {
 		t.Fatal(err)
 	}
+
 	want := &V2EventResponse{
 		Status:   "ok",
 		DedupKey: "yes",
 		Message:  "ok",
 	}
+
 	testEqual(t, want, res)
 }
diff --git a/extension_schema_test.go b/extension_schema_test.go
index 233a854..955ae3d 100644
--- a/extension_schema_test.go
+++ b/extension_schema_test.go
@@ -15,7 +15,7 @@ func TestExtensionSchema_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListExtensionSchemaOptions{
 		APIListObject: listObj,
 		Query:         "foo",
@@ -55,7 +55,7 @@ func TestExtensionSchema_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"extension_schema": {"name": "foo", "id": "1", "send_types": ["trigger", "acknowledge", "resolve"]}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.GetExtensionSchema("1")
 
diff --git a/extension_test.go b/extension_test.go
index 6679e8f..6400e2c 100644
--- a/extension_test.go
+++ b/extension_test.go
@@ -16,7 +16,7 @@ func TestExtension_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListExtensionOptions{
 		APIListObject: listObj,
 		Query:         "foo",
@@ -77,7 +77,7 @@ func TestExtension_Create(t *testing.T) {
 		}
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	want1 := &Extension{
 		Name: "foo",
@@ -116,7 +116,7 @@ func TestExtension_Delete(t *testing.T) {
 		w.WriteHeader(http.StatusNoContent)
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	if err := client.DeleteExtension("1"); err != nil {
 		t.Fatal(err)
@@ -132,7 +132,7 @@ func TestExtension_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"extension": {"name": "foo", "id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.GetExtension("1")
 
@@ -180,7 +180,7 @@ func TestExtension_Update(t *testing.T) {
 		_, _ = w.Write([]byte(`{"extension": {"name": "foo", "id": "2", "endpoint_url": "expected_url"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	want1 := &Extension{
 		Name: "foo",
diff --git a/incident_test.go b/incident_test.go
index ae98b7f..45afd72 100644
--- a/incident_test.go
+++ b/incident_test.go
@@ -16,7 +16,7 @@ func TestIncident_List(t *testing.T) {
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
 	var opts ListIncidentsOptions
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.ListIncidents(opts)
 
@@ -49,7 +49,7 @@ func TestIncident_Create(t *testing.T) {
 		testMethod(t, r, "POST")
 		_, _ = w.Write([]byte(`{"incident": {"title": "foo", "id": "1", "urgency": "low"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	from := "foo@bar.com"
 	res, err := client.CreateIncident(from, input)
 
@@ -74,7 +74,7 @@ func TestIncident_Manage_status(t *testing.T) {
 		_, _ = w.Write([]byte(`{"incidents": [{"title": "foo", "id": "1", "status": "acknowledged"}]}`))
 	})
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	from := "foo@bar.com"
 
 	input := []ManageIncidentsOptions{
@@ -111,7 +111,7 @@ func TestIncident_Manage_priority(t *testing.T) {
 		_, _ = w.Write([]byte(`{"incidents": [{"title": "foo", "id": "1", "priority": {"id": "PRIORITY_ID_HERE", "type": "priority_reference"}}]}`))
 	})
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	from := "foo@bar.com"
 
 	input := []ManageIncidentsOptions{
@@ -156,7 +156,7 @@ func TestIncident_Manage_assignments(t *testing.T) {
 		_, _ = w.Write([]byte(`{"incidents": [{"title": "foo", "id": "1", "assignments": [{"assignee":{"id": "ASSIGNEE_ONE", "type": "user_reference"}},{"assignee":{"id": "ASSIGNEE_TWO", "type": "user_reference"}}]}]}`))
 	})
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	from := "foo@bar.com"
 
 	input := []ManageIncidentsOptions{
@@ -218,7 +218,7 @@ func TestIncident_Merge(t *testing.T) {
 		testMethod(t, r, "PUT")
 		_, _ = w.Write([]byte(`{"incident": {"title": "foo", "id": "1"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	from := "foo@bar.com"
 
 	input := []MergeIncidentsOptions{{ID: "2", Type: "incident"}}
@@ -240,7 +240,7 @@ func TestIncident_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"incident": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	id := "1"
 	res, err := client.GetIncident(id)
@@ -262,7 +262,7 @@ func TestIncident_ListIncidentNotes(t *testing.T) {
 		_, _ = w.Write([]byte(`{"notes": [{"id": "1","content":"foo"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 
 	res, err := client.ListIncidentNotes(id)
@@ -289,7 +289,7 @@ func TestIncident_ListIncidentAlerts(t *testing.T) {
 		_, _ = w.Write([]byte(`{"alerts": [{"id": "1","summary":"foo"}]}`))
 	})
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 
 	res, err := client.ListIncidentAlerts(id)
@@ -321,7 +321,7 @@ func TestIncident_ListIncidentAlertsWithOpts(t *testing.T) {
 		_, _ = w.Write([]byte(`{"alerts": [{"id": "1","summary":"foo"}]}`))
 	})
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 
 	alertOpts := ListIncidentAlertsOptions{
@@ -362,7 +362,7 @@ func TestIncident_CreateIncidentNote(t *testing.T) {
 		testMethod(t, r, "POST")
 		_, _ = w.Write([]byte(`{"note": {"id": "1","content": "foo"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	err := client.CreateIncidentNote(id, input)
 	if err != nil {
@@ -383,7 +383,7 @@ func TestIncident_CreateIncidentNoteWithResponse(t *testing.T) {
 		testMethod(t, r, "POST")
 		_, _ = w.Write([]byte(`{"note": {"id": "1","content": "foo"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	res, err := client.CreateIncidentNoteWithResponse(id, input)
 
@@ -407,7 +407,7 @@ func TestIncident_SnoozeIncident(t *testing.T) {
 		testMethod(t, r, "POST")
 		_, _ = w.Write([]byte(`{"incident": {"id": "1", "pending_actions": [{"type": "unacknowledge", "at":"2019-12-31T16:58:35Z"}]}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	var duration uint = 3600
 	id := "1"
 
@@ -426,7 +426,7 @@ func TestIncident_SnoozeIncidentWithResponse(t *testing.T) {
 		testMethod(t, r, "POST")
 		_, _ = w.Write([]byte(`{"incident": {"id": "1", "pending_actions": [{"type": "unacknowledge", "at":"2019-12-31T16:58:35Z"}]}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	var duration uint = 3600
 	id := "1"
 
@@ -458,7 +458,7 @@ func TestIncident_ListLogEntries(t *testing.T) {
 		_, _ = w.Write([]byte(`{"log_entries": [{"id": "1","summary":"foo"}]}`))
 	})
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	entriesOpts := ListIncidentLogEntriesOptions{
 		APIListObject: listObj,
@@ -498,7 +498,7 @@ func TestIncident_ListLogEntriesSinceUntil(t *testing.T) {
 		_, _ = w.Write([]byte(`{"log_entries": [{"id": "1","summary":"foo"}]}`))
 	})
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	entriesOpts := ListIncidentLogEntriesOptions{
 		APIListObject: listObj,
@@ -559,7 +559,7 @@ func TestIncident_ResponderRequest(t *testing.T) {
 	}
 }`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	from := "foo@bar.com"
 
 	r := ResponderRequestTarget{}
@@ -607,7 +607,7 @@ func TestIncident_GetAlert(t *testing.T) {
 		_, _ = w.Write([]byte(`{"alert": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	incidentID := "1"
 	alertID := "1"
@@ -636,7 +636,7 @@ func TestIncident_ManageAlerts(t *testing.T) {
 		_, _ = w.Write([]byte(`{"alerts": [{"id": "1"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	incidentID := "1"
 
diff --git a/log_entry_test.go b/log_entry_test.go
index 310088f..72f45f4 100644
--- a/log_entry_test.go
+++ b/log_entry_test.go
@@ -16,7 +16,7 @@ func TestLogEntry_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	entriesOpts := ListLogEntriesOptions{
 		APIListObject: listObj,
 		Includes:      []string{},
@@ -54,7 +54,7 @@ func TestLogEntry_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"log_entry": {"id": "1", "summary": "foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	opts := GetLogEntryOptions{TimeZone: "UTC", Includes: []string{}}
 	res, err := client.GetLogEntry(id, opts)
diff --git a/maintenance_window_test.go b/maintenance_window_test.go
index b0af27d..1611cf3 100644
--- a/maintenance_window_test.go
+++ b/maintenance_window_test.go
@@ -16,7 +16,7 @@ func TestMaintenanceWindow_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListMaintenanceWindowsOptions{
 		APIListObject: listObj,
 		Query:         "foo",
@@ -58,7 +58,7 @@ func TestMaintenanceWindow_Create(t *testing.T) {
 		_, _ = w.Write([]byte(`{"maintenance_window": {"description": "foo", "id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.CreateMaintenanceWindow(from, input)
 
@@ -87,7 +87,7 @@ func TestMaintenanceWindow_Create_NoFrom(t *testing.T) {
 		_, _ = w.Write([]byte(`{"maintenance_window": {"description": "foo", "id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.CreateMaintenanceWindow(from, input)
 
@@ -113,7 +113,7 @@ func TestMaintenanceWindow_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 		w.WriteHeader(http.StatusNoContent)
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	err := client.DeleteMaintenanceWindow("1")
 	if err != nil {
 		t.Fatal(err)
@@ -130,7 +130,7 @@ func TestMaintenanceWindow_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"maintenance_window": {"description": "foo", "id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	opts := GetMaintenanceWindowOptions{Includes: []string{}}
 	res, err := client.GetMaintenanceWindow(id, opts)
@@ -164,7 +164,7 @@ func TestMaintenanceWindow_Update(t *testing.T) {
 		testMethod(t, r, "PUT")
 		_, _ = w.Write([]byte(`{"maintenance_window": {"description": "foo", "id": "1"}}`))
 	})
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.UpdateMaintenanceWindow(input)
 
diff --git a/notification_test.go b/notification_test.go
index d525b3f..99fb3ee 100644
--- a/notification_test.go
+++ b/notification_test.go
@@ -16,7 +16,7 @@ func TestNotification_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListNotificationOptions{
 		APIListObject: listObj,
 		Includes:      []string{},
diff --git a/on_call_test.go b/on_call_test.go
index 148d637..27502ce 100644
--- a/on_call_test.go
+++ b/on_call_test.go
@@ -16,7 +16,7 @@ func TestOnCall_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListOnCallOptions{
 		APIListObject:       listObj,
 		TimeZone:            "UTC",
diff --git a/priorities_test.go b/priorities_test.go
index aa74c9f..d07619a 100644
--- a/priorities_test.go
+++ b/priorities_test.go
@@ -16,7 +16,7 @@ func TestPriorities_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.ListPriorities()
 
diff --git a/ruleset_test.go b/ruleset_test.go
index 64ba041..17477a5 100644
--- a/ruleset_test.go
+++ b/ruleset_test.go
@@ -15,7 +15,7 @@ func TestRuleset_List(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rulesets": [{"id": "1"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	res, err := client.ListRulesets()
 	if err != nil {
@@ -42,7 +42,7 @@ func TestRuleset_Create(t *testing.T) {
 		_, _ = w.Write([]byte(`{"ruleset": {"id": "1", "name": "foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &Ruleset{
 		Name: "foo",
 	}
@@ -69,7 +69,7 @@ func TestRuleset_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"ruleset": {"id": "1", "name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ruleSetID := "1"
 
 	res, _, err := client.GetRuleset(ruleSetID)
@@ -95,7 +95,7 @@ func TestRuleset_Update(t *testing.T) {
 		_, _ = w.Write([]byte(`{"ruleset": {"id": "1", "name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &Ruleset{
 		ID:   "1",
 		Name: "foo",
@@ -122,7 +122,7 @@ func TestRuleset_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	err := client.DeleteRuleset(id)
 	if err != nil {
@@ -140,7 +140,7 @@ func TestRuleset_ListRules(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rules": [{"id": "1"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	rulesetID := "1"
 	res, err := client.ListRulesetRules(rulesetID)
@@ -168,7 +168,7 @@ func TestRuleset_GetRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rule": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	rulesetID := "1"
 	ruleID := "1"
@@ -193,7 +193,7 @@ func TestRuleset_CreateRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rule": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	rulesetID := "1"
 	rule := &RulesetRule{}
@@ -219,7 +219,7 @@ func TestRuleset_UpdateRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rule": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	rulesetID := "1"
 	ruleID := "1"
@@ -245,7 +245,7 @@ func TestRuleset_DeleteRule(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ruleID := "1"
 	rulesetID := "1"
 
diff --git a/schedule_test.go b/schedule_test.go
index fb662d3..7d3596c 100644
--- a/schedule_test.go
+++ b/schedule_test.go
@@ -16,7 +16,7 @@ func TestSchedule_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListSchedulesOptions{
 		APIListObject: listObj,
 		Query:         "foo",
@@ -51,7 +51,7 @@ func TestSchedule_Create(t *testing.T) {
 		_, _ = w.Write([]byte(`{"schedule": {"id": "1","summary":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := Schedule{
 		APIObject: APIObject{
 			ID:      "1",
@@ -84,7 +84,7 @@ func TestSchedule_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	err := client.DeleteSchedule(id)
 	if err != nil {
@@ -102,7 +102,7 @@ func TestSchedule_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"schedule": {"id": "1","summary":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
 
 	input := "1"
@@ -137,7 +137,7 @@ func TestSchedule_Update(t *testing.T) {
 		_, _ = w.Write([]byte(`{"schedule": {"id": "1","summary":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	id := "1"
 	sched := Schedule{
@@ -172,7 +172,7 @@ func TestSchedule_ListOverrides(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListOverridesOptions{
 		APIListObject: listObj,
 		Since:         "foo",
@@ -209,7 +209,7 @@ func TestSchedule_CreateOverride(t *testing.T) {
 		_, _ = w.Write([]byte(`{"override": {"id": "1", "start": "foo", "end": "bar"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := Override{
 		Start: "foo",
 		End:   "bar",
@@ -239,7 +239,7 @@ func TestSchedule_DeleteOverride(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	schedID := "1"
 	overID := "1"
 	err := client.DeleteOverride(schedID, overID)
@@ -259,7 +259,7 @@ func TestSchedule_ListOnCallUsers(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListOnCallUsersOptions{
 		APIListObject: listObj,
 		Since:         "foo",
diff --git a/service_dependency_test.go b/service_dependency_test.go
index e049dc3..e7afce8 100644
--- a/service_dependency_test.go
+++ b/service_dependency_test.go
@@ -15,7 +15,7 @@ func TestBusinessServiceDependency_List(t *testing.T) {
 		_, _ = w.Write([]byte(`{"relationships": [{"id": "1","dependent_service":{"id":"1"},"supporting_service":{"id":"1"},"type":"service_dependency"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	bServeID := "1"
 	res, _, err := client.ListBusinessServiceDependencies(bServeID)
 	if err != nil {
@@ -49,7 +49,7 @@ func TestTechnicalServiceDependency_List(t *testing.T) {
 		_, _ = w.Write([]byte(`{"relationships": [{"id": "1","dependent_service":{"id":"1"},"supporting_service":{"id":"1"},"type":"service_dependency"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	bServeID := "1"
 	res, _, err := client.ListTechnicalServiceDependencies(bServeID)
 	if err != nil {
@@ -83,7 +83,7 @@ func TestServiceDependency_Associate(t *testing.T) {
 		_, _ = w.Write([]byte(`{"relationships": [{"id": "1","dependent_service":{"id":"1"},"supporting_service":{"id":"1"},"type":"service_dependency"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &ListServiceDependencies{
 		Relationships: []*ServiceDependency{
 			{
@@ -129,7 +129,7 @@ func TestServiceDependency_Disassociate(t *testing.T) {
 		_, _ = w.Write([]byte(`{"relationships": [{"id": "1","dependent_service":{"id":"1"},"supporting_service":{"id":"1"},"type":"service_dependency"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &ListServiceDependencies{
 		Relationships: []*ServiceDependency{
 			{
diff --git a/service_test.go b/service_test.go
index 6223977..6f05a0f 100644
--- a/service_test.go
+++ b/service_test.go
@@ -19,7 +19,7 @@ func TestService_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListServiceOptions{
 		APIListObject: listObj,
 		TeamIDs:       []string{},
@@ -71,7 +71,7 @@ func TestService_ListPaginated(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 1, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListServiceOptions{
 		APIListObject: listObj,
 		TeamIDs:       []string{},
@@ -111,7 +111,7 @@ func TestService_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"service": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	id := "1"
 	opts := &GetServiceOptions{
@@ -142,7 +142,7 @@ func TestService_Create(t *testing.T) {
 		_, _ = w.Write([]byte(`{"service": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := Service{
 		Name: "foo",
 	}
@@ -171,7 +171,7 @@ func TestService_CreateWithAlertGroupParamsTime(t *testing.T) {
 		_, _ = w.Write([]byte(`{"service": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := Service{
 		Name: "foo",
 		AlertGroupingParameters: &AlertGroupingParameters{
@@ -206,7 +206,7 @@ func TestService_CreateWithAlertGroupParamsContentBased(t *testing.T) {
 		_, _ = w.Write([]byte(`{"service": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := Service{
 		Name: "foo",
 		AlertGroupingParameters: &AlertGroupingParameters{
@@ -242,7 +242,7 @@ func TestService_CreateWithAlertGroupParamsIntelligent(t *testing.T) {
 		_, _ = w.Write([]byte(`{"service": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := Service{
 		Name: "foo",
 		AlertGroupingParameters: &AlertGroupingParameters{
@@ -274,7 +274,7 @@ func TestService_Update(t *testing.T) {
 		_, _ = w.Write([]byte(`{"service": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	input := Service{
 		APIObject: APIObject{
@@ -306,7 +306,7 @@ func TestService_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	err := client.DeleteService(id)
 	if err != nil {
@@ -324,7 +324,7 @@ func TestService_CreateIntegration(t *testing.T) {
 		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := Integration{
 		Name: "foo",
 	}
@@ -355,7 +355,7 @@ func TestService_GetIntegration(t *testing.T) {
 		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := GetIntegrationOptions{
 		Includes: []string{},
 	}
@@ -387,7 +387,7 @@ func TestService_UpdateIntegration(t *testing.T) {
 		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := Integration{
 		APIObject: APIObject{
 			ID: "1",
@@ -420,7 +420,7 @@ func TestService_DeleteIntegration(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	servID := "1"
 	intID := "1"
 	err := client.DeleteIntegration(servID, intID)
@@ -439,7 +439,7 @@ func TestService_ListRules(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rules": [{"id": "1"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	serviceID := "1"
 	res, err := client.ListServiceRulesPaginated(context.Background(), serviceID)
@@ -461,7 +461,7 @@ func TestService_CreateServiceRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rule": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	serviceID := "1"
 	rule := ServiceRule{}
@@ -487,7 +487,7 @@ func TestService_GetServiceRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rule": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	serviceID := "1"
 	ruleID := "1"
@@ -512,7 +512,7 @@ func TestService_UpdateServiceRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"rule": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	serviceID := "1"
 	ruleID := "1"
@@ -538,7 +538,7 @@ func TestService_DeleteServiceRule(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	serviceID := "1"
 	ruleID := "1"
 
diff --git a/tag_test.go b/tag_test.go
index ea11c3d..65cc483 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -17,7 +17,7 @@ func TestTag_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListTagOptions{
 		APIListObject: listObj,
 		Query:         "MyTag",
@@ -50,7 +50,7 @@ func TestTag_Create(t *testing.T) {
 		_, _ = w.Write([]byte(`{"tag": {"id": "1","Label":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &Tag{
 		Label: "foo",
 	}
@@ -78,7 +78,7 @@ func TestTag_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	err := client.DeleteTag(id)
 	if err != nil {
@@ -96,7 +96,7 @@ func TestTag_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"tag": {"id": "1","label":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	res, _, err := client.GetTag(id)
 
@@ -122,7 +122,7 @@ func TestTag_AssignAdd(t *testing.T) {
 		testMethod(t, r, "POST")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ta := &TagAssignments{
 		Add: []*TagAssignment{
 			{
@@ -151,7 +151,7 @@ func TestTag_AssignRemove(t *testing.T) {
 		testMethod(t, r, "POST")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ta := &TagAssignments{
 		Remove: []*TagAssignment{
 			{
@@ -177,7 +177,7 @@ func TestTag_GetUsersByTag(t *testing.T) {
 		_, _ = w.Write([]byte(`{"users": [{"id": "1"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	tid := "1"
 
 	res, err := client.GetUsersByTag(tid)
@@ -205,7 +205,7 @@ func TestTag_GetTeamsByTag(t *testing.T) {
 		_, _ = w.Write([]byte(`{"teams": [{"id": "1"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	tid := "1"
 
 	res, err := client.GetTeamsByTag(tid)
@@ -233,7 +233,7 @@ func TestTag_GetEscalationPoliciesByTag(t *testing.T) {
 		_, _ = w.Write([]byte(`{"escalation_policies": [{"id": "1"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	tid := "1"
 
 	res, err := client.GetEscalationPoliciesByTag(tid)
@@ -261,7 +261,7 @@ func TestTag_GetTagsForEntity(t *testing.T) {
 		_, _ = w.Write([]byte(`{"tags": [{"id": "1"}]}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	eid := "1"
 	e := "escalation_policies"
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
diff --git a/team_test.go b/team_test.go
index 1d35895..5f9a385 100644
--- a/team_test.go
+++ b/team_test.go
@@ -19,7 +19,7 @@ func TestTeam_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListTeamOptions{
 		APIListObject: listObj,
 		Query:         "foo",
@@ -53,7 +53,7 @@ func TestTeam_Create(t *testing.T) {
 		_, _ = w.Write([]byte(`{"team": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := &Team{
 		Name: "foo",
 	}
@@ -81,7 +81,7 @@ func TestTeam_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	err := client.DeleteTeam(id)
 	if err != nil {
@@ -99,7 +99,7 @@ func TestTeam_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"team": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	res, err := client.GetTeam(id)
 
@@ -126,7 +126,7 @@ func TestTeam_Update(t *testing.T) {
 		_, _ = w.Write([]byte(`{"team": {"id": "1","name":"foo"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 
 	input := &Team{
 		APIObject: APIObject{
@@ -159,7 +159,7 @@ func TestTeam_RemoveEscalationPolicyFromTeam(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	teamID := "1"
 	epID := "1"
 
@@ -178,7 +178,7 @@ func TestTeam_AddEscalationPolicyToTeam(t *testing.T) {
 		testMethod(t, r, "PUT")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	teamID := "1"
 	epID := "1"
 
@@ -197,7 +197,7 @@ func TestTeam_RemoveUserFromTeam(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	teamID := "1"
 	userID := "1"
 
@@ -216,7 +216,7 @@ func TestTeam_AddUserToTeam(t *testing.T) {
 		testMethod(t, r, "PUT")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	teamID := "1"
 	userID := "1"
 
@@ -359,7 +359,7 @@ func TestListMembersSuccess(t *testing.T) {
 		fmt.Fprint(w, page)
 	})
 
-	api := &Client{apiEndpoint: server.URL, authToken: testAPIKey, HTTPClient: defaultHTTPClient}
+	api := defaultTestClient(server.URL, testAPIKey)
 	members, err := api.ListMembers(testValidTeamID, ListMembersOptions{})
 	if err != nil {
 		t.Fatalf("Failed to get members: %v", err)
@@ -371,7 +371,7 @@ func TestListMembersSuccess(t *testing.T) {
 }
 
 func TestListMembersError(t *testing.T) {
-	api := &Client{apiEndpoint: testBadURL, authToken: testAPIKey, HTTPClient: defaultHTTPClient}
+	api := defaultTestClient(testBadURL, testAPIKey)
 	members, err := api.ListMembers(testValidTeamID, ListMembersOptions{})
 	if err == nil {
 		t.Fatalf("Expected error, got nil")
@@ -394,7 +394,7 @@ func TestListAllMembersSuccessMultiplePages(t *testing.T) {
 		currentPage++
 	})
 
-	api := &Client{apiEndpoint: server.URL, authToken: testAPIKey, HTTPClient: defaultHTTPClient}
+	api := defaultTestClient(server.URL, testAPIKey)
 
 	members, err := api.ListAllMembers(testValidTeamID)
 	if err != nil {
@@ -407,7 +407,7 @@ func TestListAllMembersSuccessMultiplePages(t *testing.T) {
 }
 
 func TestListAllMembersError(t *testing.T) {
-	api := &Client{apiEndpoint: testBadURL, authToken: testAPIKey, HTTPClient: defaultHTTPClient}
+	api := defaultTestClient(testBadURL, testAPIKey)
 	members, err := api.ListAllMembers(testValidTeamID)
 	if err == nil {
 		t.Fatalf("Expected error, got nil")
diff --git a/user_test.go b/user_test.go
index 2d04c53..a366591 100644
--- a/user_test.go
+++ b/user_test.go
@@ -16,7 +16,7 @@ func TestUser_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListUsersOptions{
 		APIListObject: listObj,
 		Query:         "foo",
@@ -52,7 +52,7 @@ func TestUser_Create(t *testing.T) {
 		_, _ = w.Write([]byte(`{"user": {"id": "1", "email":"foo@bar.com"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := User{
 		Email: "foo@bar.com",
 	}
@@ -80,7 +80,7 @@ func TestUser_Delete(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	id := "1"
 	err := client.DeleteUser(id)
 	if err != nil {
@@ -98,7 +98,7 @@ func TestUser_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"user": {"id": "1", "email":"foo@bar.com"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	userID := "1"
 	opts := GetUserOptions{
 		Includes: []string{},
@@ -128,7 +128,7 @@ func TestUser_Update(t *testing.T) {
 		_, _ = w.Write([]byte(`{"user": {"id": "1", "email":"foo@bar.com"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	input := User{
 		APIObject: APIObject{
 			ID: "1",
@@ -160,7 +160,7 @@ func TestUser_GetCurrent(t *testing.T) {
 		_, _ = w.Write([]byte(`{"user": {"id": "1", "email":"foo@bar.com"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := GetCurrentUserOptions{
 		Includes: []string{},
 	}
@@ -190,7 +190,7 @@ func TestUser_ListContactMethods(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ID := "1"
 
 	res, err := client.ListUserContactMethods(ID)
@@ -220,7 +220,7 @@ func TestUser_GetContactMethod(t *testing.T) {
 		_, _ = w.Write([]byte(`{"contact_method": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	methodID := "1"
 	userID := "1"
 
@@ -246,7 +246,7 @@ func TestUser_CreateContactMethod(t *testing.T) {
 		_, _ = w.Write([]byte(`{"contact_method": {"id": "1", "type": "email_contact_method"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	userID := "1"
 	contactMethod := ContactMethod{
 		Type: "email_contact_method",
@@ -273,7 +273,7 @@ func TestUser_DeleteContactMethod(t *testing.T) {
 		testMethod(t, r, "DELETE")
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	userID := "1"
 	contactMethodID := "1"
 
@@ -293,7 +293,7 @@ func TestUser_UpdateContactMethod(t *testing.T) {
 		_, _ = w.Write([]byte(`{"contact_method": {"id": "1", "type": "email_contact_method"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	userID := "1"
 	contactMethod := ContactMethod{
 		ID:   "1",
@@ -322,7 +322,7 @@ func TestUser_GetUserNotificationRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"notification_rule": {"id": "1", "start_delay_in_minutes": 1, "urgency": "low", "contact_method": {"id": "1"}}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ruleID := "1"
 	userID := "1"
 
@@ -353,7 +353,7 @@ func TestUser_CreateUserNotificationRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"notification_rule": {"id": "1", "start_delay_in_minutes": 1, "urgency": "low", "contact_method": {"id": "1"}}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	userID := "1"
 	rule := NotificationRule{
 		Type: "email_contact_method",
@@ -386,7 +386,7 @@ func TestUser_ListUserNotificationRules(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	ID := "1"
 
 	res, err := client.ListUserNotificationRules(ID)
@@ -421,7 +421,7 @@ func TestUser_UpdateUserNotificationRule(t *testing.T) {
 		_, _ = w.Write([]byte(`{"notification_rule": {"id": "1", "start_delay_in_minutes": 1, "urgency": "low", "contact_method": {"id": "1"}}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	userID := "1"
 	rule := NotificationRule{
 		ID:   "1",
@@ -455,7 +455,7 @@ func TestUser_DeleteUserNotificationRule(t *testing.T) {
 	userID := "1"
 	ruleID := "1"
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	if err := client.DeleteUserNotificationRule(userID, ruleID); err != nil {
 		t.Fatal(err)
 	}
diff --git a/vendor_test.go b/vendor_test.go
index 5f8054a..534c81b 100644
--- a/vendor_test.go
+++ b/vendor_test.go
@@ -16,7 +16,7 @@ func TestVendor_List(t *testing.T) {
 	})
 
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	opts := ListVendorOptions{
 		APIListObject: listObj,
 		Query:         "foo",
@@ -50,7 +50,7 @@ func TestVendor_Get(t *testing.T) {
 		_, _ = w.Write([]byte(`{"vendor": {"id": "1"}}`))
 	})
 
-	client := &Client{apiEndpoint: server.URL, authToken: "foo", HTTPClient: defaultHTTPClient}
+	client := defaultTestClient(server.URL, "foo")
 	venID := "1"
 
 	res, err := client.GetVendor(venID)