Merge pull request #32 from rackspace/cloud-servers-1

Epic refactoring to improve testability.
diff --git a/api.go b/api.go
deleted file mode 100644
index 069d4c9..0000000
--- a/api.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package gophercloud
-
-import "strings"
-
-// globalContext is the, well, "global context."
-// Most of this SDK is written in a manner to facilitate easier testing,
-// which doesn't require all the configuration a real-world application would require.
-// However, for real-world deployments, applications should be able to rely on a consistent configuration of providers, etc.
-var globalContext *Context
-
-// providers is the set of supported providers.
-var providers = map[string]Provider{
-	"rackspace-us": Provider{
-		AuthEndpoint: "https://identity.api.rackspacecloud.com/v2.0/tokens",
-	},
-	"rackspace-uk": Provider{
-		AuthEndpoint: "https://lon.identity.api.rackspacecloud.com/v2.0/tokens",
-	},
-}
-
-// Initialize the global context to sane configuration.
-// The Go runtime ensures this function is called before main(),
-// thus guaranteeing proper configuration before your application ever runs.
-func init() {
-	globalContext = TestContext()
-	for name, descriptor := range providers {
-		globalContext.RegisterProvider(name, descriptor)
-	}
-}
-
-// Authenticate() grants access to the OpenStack-compatible provider API.
-//
-// Providers are identified through a unique key string.
-// Specifying an unsupported provider will result in an ErrProvider error.
-//
-// The supplied AuthOptions instance allows the client to specify only those credentials
-// relevant for the authentication request.  At present, support exists for OpenStack
-// Identity V2 API only; support for V3 will become available as soon as documentation for it
-// becomes readily available.
-//
-// For Identity V2 API requirements, you must provide at least the Username and Password
-// options.  The TenantId field is optional, and defaults to "".
-func Authenticate(provider string, options AuthOptions) (*Access, error) {
-	return globalContext.Authenticate(provider, options)
-}
-
-// ApiCriteria provides one or more criteria for the SDK to look for appropriate endpoints.
-// Fields left unspecified or otherwise set to their zero-values are assumed to not be
-// relevant, and do not participate in the endpoint search.
-type ApiCriteria struct {
-	// Name specifies the desired service catalog entry name.
-	Name string
-
-	// Region specifies the desired endpoint region.
-	Region string
-
-	// VersionId specifies the desired version of the endpoint.
-	// Note that this field is matched exactly, and is (at present)
-	// opaque to Gophercloud.  Thus, requesting a version 2
-	// endpoint will _not_ match a version 3 endpoint.
-	VersionId string
-
-	// The UrlChoice field inidicates whether or not gophercloud
-	// should use the public or internal endpoint URL if a
-	// candidate endpoint is found.
-	UrlChoice int
-}
-
-// The choices available for UrlChoice.  See the ApiCriteria structure for details.
-const (
-	PublicURL = iota
-	InternalURL
-)
-
-// ComputeProvider instances encapsulate a Cloud Servers API, should one exist in the service catalog
-// for your provider.
-type ComputeProvider interface {
-	ListServers() ([]Server, error)
-}
-
-// AccessProvider instances encapsulate a Keystone authentication interface.
-type AccessProvider interface {
-	// FirstEndpointUrlByCriteria searches through the service catalog for the first
-	// matching entry endpoint fulfilling the provided criteria.  If nothing found,
-	// return "".  Otherwise, return either the public or internal URL for the
-	// endpoint, depending on both its existence and the setting of the ApiCriteria.UrlChoice
-	// field.
-	FirstEndpointUrlByCriteria(ApiCriteria) string
-}
-
-// genericCloudProvider structures provide the implementation for generic OpenStack-compatible
-// ComputeProvider interfaces.
-type genericCloudProvider struct {
-	// endpoint refers to the provider's API endpoint base URL.  This will be used to construct
-	// and issue queries.
-	endpoint string
-}
-
-func ComputeApi(acc AccessProvider, criteria ApiCriteria) (ComputeProvider, error) {
-	url := acc.FirstEndpointUrlByCriteria(criteria)
-	if url == "" {
-		return nil, ErrEndpoint
-	}
-
-	gcp := &genericCloudProvider{
-		endpoint: url,
-	}
-
-	return gcp, nil
-}
-
-// See AccessProvider interface definition for details.
-func (a *Access) FirstEndpointUrlByCriteria(ac ApiCriteria) string {
-	ep := FindFirstEndpointByCriteria(a.ServiceCatalog, ac)
-	urls := []string{ep.PublicURL, ep.InternalURL}
-	return urls[ac.UrlChoice]
-}
-
-// Given a set of criteria to match on, locate the first candidate endpoint
-// in the provided service catalog.
-//
-// If nothing found, the result will be a zero-valued EntryEndpoint (all URLs
-// set to "").
-func FindFirstEndpointByCriteria(entries []CatalogEntry, ac ApiCriteria) EntryEndpoint {
-	rgn := strings.ToUpper(ac.Region)
-
-	for _, entry := range entries {
-		if (ac.Name != "") && (ac.Name != entry.Name) {
-			continue
-		}
-
-		for _, endpoint := range entry.Endpoints {
-			if (ac.Region != "") && (rgn != strings.ToUpper(endpoint.Region)) {
-				continue
-			}
-
-			if (ac.VersionId != "") && (ac.VersionId != endpoint.VersionId) {
-				continue
-			}
-
-			return endpoint
-		}
-	}
-	return EntryEndpoint{}
-}
-
-// See the ComputeProvider interface for details.
-func (gcp *genericCloudProvider) ListServers() ([]Server, error) {
-	return nil, nil
-}
-
-// Server structures provide data about a server running in your provider's cloud.
-type Server struct {
-	Id string
-}
diff --git a/authenticate.go b/authenticate.go
index 836c21b..d460673 100644
--- a/authenticate.go
+++ b/authenticate.go
@@ -39,9 +39,7 @@
 }
 
 // Access encapsulates the API token and its relevant fields, as well as the
-// services catalog that Identity API returns once authenticated.  You'll probably
-// rarely use this record directly, unless you intend on marshalling or unmarshalling
-// Identity API JSON records yourself.
+// services catalog that Identity API returns once authenticated.
 type Access struct {
 	Token          Token
 	ServiceCatalog []CatalogEntry
@@ -129,3 +127,10 @@
 	})
 	return access, err
 }
+
+// See AccessProvider interface definition for details.
+func (a *Access) FirstEndpointUrlByCriteria(ac ApiCriteria) string {
+	ep := FindFirstEndpointByCriteria(a.ServiceCatalog, ac)
+	urls := []string{ep.PublicURL, ep.InternalURL}
+	return urls[ac.UrlChoice]
+}
diff --git a/authenticate_test.go b/authenticate_test.go
index f077dab..a19fec4 100644
--- a/authenticate_test.go
+++ b/authenticate_test.go
@@ -1,10 +1,7 @@
 package gophercloud
 
 import (
-	"encoding/json"
-	"io/ioutil"
 	"net/http"
-	"strings"
 	"testing"
 )
 
@@ -55,85 +52,9 @@
 }
 `
 
-type testTransport struct {
-	called   int
-	response string
-}
-
-func (t *testTransport) RoundTrip(req *http.Request) (rsp *http.Response, err error) {
-	t.called++
-
-	headers := make(http.Header)
-	headers.Add("Content-Type", "application/xml; charset=UTF-8")
-
-	body := ioutil.NopCloser(strings.NewReader(t.response))
-
-	rsp = &http.Response{
-		Status:           "200 OK",
-		StatusCode:       200,
-		Proto:            "HTTP/1.1",
-		ProtoMajor:       1,
-		ProtoMinor:       1,
-		Header:           headers,
-		Body:             body,
-		ContentLength:    -1,
-		TransferEncoding: nil,
-		Close:            true,
-		Trailer:          nil,
-		Request:          req,
-	}
-	return
-}
-
-type tenantIdCheckTransport struct {
-	expectTenantId bool
-	tenantIdFound  bool
-}
-
-func (t *tenantIdCheckTransport) RoundTrip(req *http.Request) (rsp *http.Response, err error) {
-	var authContainer *AuthContainer
-
-	headers := make(http.Header)
-	headers.Add("Content-Type", "application/xml; charset=UTF-8")
-
-	body := ioutil.NopCloser(strings.NewReader("t.response"))
-
-	rsp = &http.Response{
-		Status:           "200 OK",
-		StatusCode:       200,
-		Proto:            "HTTP/1.1",
-		ProtoMajor:       1,
-		ProtoMinor:       1,
-		Header:           headers,
-		Body:             body,
-		ContentLength:    -1,
-		TransferEncoding: nil,
-		Close:            true,
-		Trailer:          nil,
-		Request:          req,
-	}
-
-	bytes, err := ioutil.ReadAll(req.Body)
-	if err != nil {
-		return nil, err
-	}
-	err = json.Unmarshal(bytes, &authContainer)
-	if err != nil {
-		return nil, err
-	}
-	t.tenantIdFound = (authContainer.Auth.TenantId != "")
-
-	if t.tenantIdFound != t.expectTenantId {
-		rsp.Status = "500 Internal Server Error"
-		rsp.StatusCode = 500
-	}
-	return
-}
-
 func TestAuthProvider(t *testing.T) {
-	c := TestContext()
-	tt := &testTransport{}
-	c.UseCustomClient(&http.Client{
+	tt := newTransport()
+	c := TestContext().UseCustomClient(&http.Client{
 		Transport: tt,
 	})
 
@@ -165,14 +86,14 @@
 }
 
 func TestTenantIdEncoding(t *testing.T) {
-	c := TestContext()
-	tt := &tenantIdCheckTransport{}
-	c.UseCustomClient(&http.Client{
+	tt := newTransport()
+	c := TestContext().
+		UseCustomClient(&http.Client{
 		Transport: tt,
-	})
-	c.RegisterProvider("provider", Provider{AuthEndpoint: "/"})
+	}).
+		WithProvider("provider", Provider{AuthEndpoint: "/"})
 
-	tt.expectTenantId = false
+	tt.IgnoreTenantId()
 	_, err := c.Authenticate("provider", AuthOptions{
 		Username: "u",
 		Password: "p",
@@ -186,7 +107,7 @@
 		return
 	}
 
-	tt.expectTenantId = true
+	tt.ExpectTenantId()
 	_, err = c.Authenticate("provider", AuthOptions{
 		Username: "u",
 		Password: "p",
@@ -203,9 +124,9 @@
 }
 
 func TestUserNameAndPassword(t *testing.T) {
-	c := TestContext()
-	c.UseCustomClient(&http.Client{Transport: &testTransport{}})
-	c.RegisterProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
+	c := TestContext().
+		WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"}).
+		UseCustomClient(&http.Client{Transport: newTransport()})
 
 	credentials := []AuthOptions{
 		AuthOptions{},
@@ -228,11 +149,9 @@
 }
 
 func TestTokenAcquisition(t *testing.T) {
-	c := TestContext()
-	tt := &testTransport{}
-	tt.response = SUCCESSFUL_RESPONSE
-	c.UseCustomClient(&http.Client{Transport: tt})
-	c.RegisterProvider("provider", Provider{AuthEndpoint: "http://localhost"})
+	c := TestContext().
+		UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
+		WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
 
 	acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
 	if err != nil {
@@ -248,11 +167,9 @@
 }
 
 func TestServiceCatalogAcquisition(t *testing.T) {
-	c := TestContext()
-	tt := &testTransport{}
-	tt.response = SUCCESSFUL_RESPONSE
-	c.UseCustomClient(&http.Client{Transport: tt})
-	c.RegisterProvider("provider", Provider{AuthEndpoint: "http://localhost"})
+	c := TestContext().
+		UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
+		WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
 
 	acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
 	if err != nil {
@@ -279,11 +196,9 @@
 }
 
 func TestUserAcquisition(t *testing.T) {
-	c := TestContext()
-	tt := &testTransport{}
-	tt.response = SUCCESSFUL_RESPONSE
-	c.UseCustomClient(&http.Client{Transport: tt})
-	c.RegisterProvider("provider", Provider{AuthEndpoint: "http://localhost"})
+	c := TestContext().
+		UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
+		WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
 
 	acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
 	if err != nil {
diff --git a/context.go b/context.go
index daa5001..71ff757 100644
--- a/context.go
+++ b/context.go
@@ -4,6 +4,14 @@
 	"net/http"
 )
 
+// Provider structures exist for each tangible provider of OpenStack service.
+// For example, Rackspace, Hewlett-Packard, and NASA might have their own instance of this structure.
+//
+// At a minimum, a provider must expose an authentication endpoint.
+type Provider struct {
+	AuthEndpoint string
+}
+
 // Context structures encapsulate Gophercloud-global state in a manner which
 // facilitates easier unit testing.  As a user of this SDK, you'll never
 // have to use this structure, except when contributing new code to the SDK.
@@ -32,6 +40,54 @@
 // UseCustomClient configures the context to use a customized HTTP client
 // instance.  By default, TestContext() will return a Context which uses
 // the net/http package's default client instance.
-func (c *Context) UseCustomClient(hc *http.Client) {
+func (c *Context) UseCustomClient(hc *http.Client) *Context {
 	c.httpClient = hc
+	return c
+}
+
+// RegisterProvider allows a unit test to register a mythical provider convenient for testing.
+// If the provider structure lacks adequate configuration, or the configuration given has some
+// detectable error, an ErrConfiguration error will result.
+func (c *Context) RegisterProvider(name string, p Provider) error {
+	if p.AuthEndpoint == "" {
+		return ErrConfiguration
+	}
+
+	c.providerMap[name] = p
+	return nil
+}
+
+// WithProvider offers convenience for unit tests.
+func (c *Context) WithProvider(name string, p Provider) *Context {
+	err := c.RegisterProvider(name, p)
+	if err != nil {
+		panic(err)
+	}
+	return c
+}
+
+// ProviderByName will locate a provider amongst those previously registered, if it exists.
+// If the named provider has not been registered, an ErrProvider error will result.
+func (c *Context) ProviderByName(name string) (p Provider, err error) {
+	for provider, descriptor := range c.providerMap {
+		if name == provider {
+			return descriptor, nil
+		}
+	}
+	return Provider{}, ErrProvider
+}
+
+// Instantiates a Cloud Servers object for the provider given.
+func (c *Context) ComputeApi(acc AccessProvider, criteria ApiCriteria) (ComputeProvider, error) {
+	url := acc.FirstEndpointUrlByCriteria(criteria)
+	if url == "" {
+		return nil, ErrEndpoint
+	}
+
+	gcp := &genericCloudProvider{
+		endpoint: url,
+		context:  c,
+	}
+
+	return gcp, nil
 }
diff --git a/provider_test.go b/context_test.go
similarity index 100%
rename from provider_test.go
rename to context_test.go
diff --git a/global_context.go b/global_context.go
new file mode 100644
index 0000000..b071c09
--- /dev/null
+++ b/global_context.go
@@ -0,0 +1,48 @@
+package gophercloud
+
+// globalContext is the, well, "global context."
+// Most of this SDK is written in a manner to facilitate easier testing,
+// which doesn't require all the configuration a real-world application would require.
+// However, for real-world deployments, applications should be able to rely on a consistent configuration of providers, etc.
+var globalContext *Context
+
+// providers is the set of supported providers.
+var providers = map[string]Provider{
+	"rackspace-us": Provider{
+		AuthEndpoint: "https://identity.api.rackspacecloud.com/v2.0/tokens",
+	},
+	"rackspace-uk": Provider{
+		AuthEndpoint: "https://lon.identity.api.rackspacecloud.com/v2.0/tokens",
+	},
+}
+
+// Initialize the global context to sane configuration.
+// The Go runtime ensures this function is called before main(),
+// thus guaranteeing proper configuration before your application ever runs.
+func init() {
+	globalContext = TestContext()
+	for name, descriptor := range providers {
+		globalContext.RegisterProvider(name, descriptor)
+	}
+}
+
+// Authenticate() grants access to the OpenStack-compatible provider API.
+//
+// Providers are identified through a unique key string.
+// Specifying an unsupported provider will result in an ErrProvider error.
+//
+// The supplied AuthOptions instance allows the client to specify only those credentials
+// relevant for the authentication request.  At present, support exists for OpenStack
+// Identity V2 API only; support for V3 will become available as soon as documentation for it
+// becomes readily available.
+//
+// For Identity V2 API requirements, you must provide at least the Username and Password
+// options.  The TenantId field is optional, and defaults to "".
+func Authenticate(provider string, options AuthOptions) (*Access, error) {
+	return globalContext.Authenticate(provider, options)
+}
+
+// Instantiates a Cloud Servers object for the provider given.
+func ComputeApi(acc AccessProvider, criteria ApiCriteria) (ComputeProvider, error) {
+	return globalContext.ComputeApi(acc, criteria)
+}
diff --git a/interfaces.go b/interfaces.go
new file mode 100644
index 0000000..671fa1a
--- /dev/null
+++ b/interfaces.go
@@ -0,0 +1,17 @@
+package gophercloud
+
+// AccessProvider instances encapsulate a Keystone authentication interface.
+type AccessProvider interface {
+	// FirstEndpointUrlByCriteria searches through the service catalog for the first
+	// matching entry endpoint fulfilling the provided criteria.  If nothing found,
+	// return "".  Otherwise, return either the public or internal URL for the
+	// endpoint, depending on both its existence and the setting of the ApiCriteria.UrlChoice
+	// field.
+	FirstEndpointUrlByCriteria(ApiCriteria) string
+}
+
+// ComputeProvider instances encapsulate a Cloud Servers API, should one exist in the service catalog
+// for your provider.
+type ComputeProvider interface {
+	ListServers() ([]Server, error)
+}
diff --git a/provider.go b/provider.go
deleted file mode 100644
index c741c4c..0000000
--- a/provider.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package gophercloud
-
-// Provider structures exist for each tangible provider of OpenStack service.
-// For example, Rackspace, Hewlett-Packard, and NASA might have their own instance of this structure.
-//
-// At a minimum, a provider must expose an authentication endpoint.
-type Provider struct {
-	AuthEndpoint string
-}
-
-// RegisterProvider allows a unit test to register a mythical provider convenient for testing.
-// If the provider structure lacks adequate configuration, or the configuration given has some
-// detectable error, an ErrConfiguration error will result.
-func (c *Context) RegisterProvider(name string, p Provider) error {
-	if p.AuthEndpoint == "" {
-		return ErrConfiguration
-	}
-
-	c.providerMap[name] = p
-	return nil
-}
-
-// ProviderByName will locate a provider amongst those previously registered, if it exists.
-// If the named provider has not been registered, an ErrProvider error will result.
-func (c *Context) ProviderByName(name string) (p Provider, err error) {
-	for provider, descriptor := range c.providerMap {
-		if name == provider {
-			return descriptor, nil
-		}
-	}
-	return Provider{}, ErrProvider
-}
diff --git a/servers.go b/servers.go
new file mode 100644
index 0000000..f6214c0
--- /dev/null
+++ b/servers.go
@@ -0,0 +1,22 @@
+package gophercloud
+
+// genericCloudProvider structures provide the implementation for generic OpenStack-compatible
+// ComputeProvider interfaces.
+type genericCloudProvider struct {
+	// endpoint refers to the provider's API endpoint base URL.  This will be used to construct
+	// and issue queries.
+	endpoint string
+
+	// Test context (if any) in which to issue requests.
+	context *Context
+}
+
+// See the ComputeProvider interface for details.
+func (gcp *genericCloudProvider) ListServers() ([]Server, error) {
+	return nil, nil
+}
+
+// Server structures provide data about a server running in your provider's cloud.
+type Server struct {
+	Id string
+}
diff --git a/servers_test.go b/servers_test.go
new file mode 100644
index 0000000..206290f
--- /dev/null
+++ b/servers_test.go
@@ -0,0 +1,42 @@
+package gophercloud
+
+import (
+	"net/http"
+	"testing"
+)
+
+type testAccess struct {
+	public, internal              string
+	calledFirstEndpointByCriteria int
+}
+
+func (ta *testAccess) FirstEndpointUrlByCriteria(ac ApiCriteria) string {
+	ta.calledFirstEndpointByCriteria++
+	urls := []string{ta.public, ta.internal}
+	return urls[ac.UrlChoice]
+}
+
+func TestGetServersApi(t *testing.T) {
+	c := TestContext().UseCustomClient(&http.Client{Transport: newTransport().WithResponse("Hello")})
+
+	acc := &testAccess{
+		public:   "http://localhost:8080",
+		internal: "http://localhost:8086",
+	}
+
+	_, err := c.ComputeApi(acc, ApiCriteria{
+		Name:      "cloudComputeOpenStack",
+		Region:    "dfw",
+		VersionId: "2",
+	})
+
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if acc.calledFirstEndpointByCriteria != 1 {
+		t.Error("Expected FirstEndpointByCriteria to be called")
+		return
+	}
+}
diff --git a/service_catalog.go b/service_catalog.go
new file mode 100644
index 0000000..326f653
--- /dev/null
+++ b/service_catalog.go
@@ -0,0 +1,61 @@
+package gophercloud
+
+import (
+	"strings"
+)
+
+// ApiCriteria provides one or more criteria for the SDK to look for appropriate endpoints.
+// Fields left unspecified or otherwise set to their zero-values are assumed to not be
+// relevant, and do not participate in the endpoint search.
+type ApiCriteria struct {
+	// Name specifies the desired service catalog entry name.
+	Name string
+
+	// Region specifies the desired endpoint region.
+	Region string
+
+	// VersionId specifies the desired version of the endpoint.
+	// Note that this field is matched exactly, and is (at present)
+	// opaque to Gophercloud.  Thus, requesting a version 2
+	// endpoint will _not_ match a version 3 endpoint.
+	VersionId string
+
+	// The UrlChoice field inidicates whether or not gophercloud
+	// should use the public or internal endpoint URL if a
+	// candidate endpoint is found.
+	UrlChoice int
+}
+
+// The choices available for UrlChoice.  See the ApiCriteria structure for details.
+const (
+	PublicURL = iota
+	InternalURL
+)
+
+// Given a set of criteria to match on, locate the first candidate endpoint
+// in the provided service catalog.
+//
+// If nothing found, the result will be a zero-valued EntryEndpoint (all URLs
+// set to "").
+func FindFirstEndpointByCriteria(entries []CatalogEntry, ac ApiCriteria) EntryEndpoint {
+	rgn := strings.ToUpper(ac.Region)
+
+	for _, entry := range entries {
+		if (ac.Name != "") && (ac.Name != entry.Name) {
+			continue
+		}
+
+		for _, endpoint := range entry.Endpoints {
+			if (ac.Region != "") && (rgn != strings.ToUpper(endpoint.Region)) {
+				continue
+			}
+
+			if (ac.VersionId != "") && (ac.VersionId != endpoint.VersionId) {
+				continue
+			}
+
+			return endpoint
+		}
+	}
+	return EntryEndpoint{}
+}
diff --git a/api_test.go b/service_catalog_test.go
similarity index 78%
rename from api_test.go
rename to service_catalog_test.go
index 4c0bc25..4420366 100644
--- a/api_test.go
+++ b/service_catalog_test.go
@@ -4,40 +4,6 @@
 	"testing"
 )
 
-type testAccess struct {
-	public, internal              string
-	calledFirstEndpointByCriteria int
-}
-
-func (ta *testAccess) FirstEndpointUrlByCriteria(ac ApiCriteria) string {
-	ta.calledFirstEndpointByCriteria++
-	urls := []string{ta.public, ta.internal}
-	return urls[ac.UrlChoice]
-}
-
-func TestGettingComputeApi(t *testing.T) {
-	acc := &testAccess{
-		public:   "http://localhost:8080",
-		internal: "http://localhost:8086",
-	}
-
-	_, err := ComputeApi(acc, ApiCriteria{
-		Name:      "cloudComputeOpenStack",
-		Region:    "dfw",
-		VersionId: "2",
-	})
-
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if acc.calledFirstEndpointByCriteria != 1 {
-		t.Error("Expected FirstEndpointByCriteria to be called")
-		return
-	}
-}
-
 func TestFindFirstEndpointByCriteria(t *testing.T) {
 	endpoint := FindFirstEndpointByCriteria([]CatalogEntry{}, ApiCriteria{Name: "test"})
 	if endpoint.PublicURL != "" {
diff --git a/transport_double.go b/transport_double.go
new file mode 100644
index 0000000..b65d5db
--- /dev/null
+++ b/transport_double.go
@@ -0,0 +1,76 @@
+package gophercloud
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"strings"
+)
+
+type transport struct {
+	called         int
+	response       string
+	expectTenantId bool
+	tenantIdFound  bool
+}
+
+func (t *transport) RoundTrip(req *http.Request) (rsp *http.Response, err error) {
+	var authContainer *AuthContainer
+
+	t.called++
+
+	headers := make(http.Header)
+	headers.Add("Content-Type", "application/xml; charset=UTF-8")
+
+	body := ioutil.NopCloser(strings.NewReader(t.response))
+
+	rsp = &http.Response{
+		Status:           "200 OK",
+		StatusCode:       200,
+		Proto:            "HTTP/1.1",
+		ProtoMajor:       1,
+		ProtoMinor:       1,
+		Header:           headers,
+		Body:             body,
+		ContentLength:    -1,
+		TransferEncoding: nil,
+		Close:            true,
+		Trailer:          nil,
+		Request:          req,
+	}
+
+	bytes, err := ioutil.ReadAll(req.Body)
+	if err != nil {
+		return nil, err
+	}
+	err = json.Unmarshal(bytes, &authContainer)
+	if err != nil {
+		return nil, err
+	}
+	t.tenantIdFound = (authContainer.Auth.TenantId != "")
+
+	if t.tenantIdFound != t.expectTenantId {
+		rsp.Status = "500 Internal Server Error"
+		rsp.StatusCode = 500
+	}
+	return
+}
+
+func newTransport() *transport {
+	return &transport{}
+}
+
+func (t *transport) IgnoreTenantId() *transport {
+	t.expectTenantId = false
+	return t
+}
+
+func (t *transport) ExpectTenantId() *transport {
+	t.expectTenantId = true
+	return t
+}
+
+func (t *transport) WithResponse(r string) *transport {
+	t.response = r
+	return t
+}