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
+}