move unit tests into 'testing' directories
diff --git a/openstack/testing/client_test.go b/openstack/testing/client_test.go
new file mode 100644
index 0000000..37e63c1
--- /dev/null
+++ b/openstack/testing/client_test.go
@@ -0,0 +1,162 @@
+package testing
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/openstack"
+	th "github.com/gophercloud/gophercloud/testhelper"
+)
+
+func TestAuthenticatedClientV3(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	const ID = "0123456789"
+
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, `
+			{
+				"versions": {
+					"values": [
+						{
+							"status": "stable",
+							"id": "v3.0",
+							"links": [
+								{ "href": "%s", "rel": "self" }
+							]
+						},
+						{
+							"status": "stable",
+							"id": "v2.0",
+							"links": [
+								{ "href": "%s", "rel": "self" }
+							]
+						}
+					]
+				}
+			}
+		`, th.Endpoint()+"v3/", th.Endpoint()+"v2.0/")
+	})
+
+	th.Mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Add("X-Subject-Token", ID)
+
+		w.WriteHeader(http.StatusCreated)
+		fmt.Fprintf(w, `{ "token": { "expires_at": "2013-02-02T18:30:59.000000Z" } }`)
+	})
+
+	options := gophercloud.AuthOptions{
+		UserID:           "me",
+		Password:         "secret",
+		IdentityEndpoint: th.Endpoint(),
+	}
+	client, err := openstack.AuthenticatedClient(options)
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, ID, client.TokenID)
+}
+
+func TestAuthenticatedClientV2(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, `
+			{
+				"versions": {
+					"values": [
+						{
+							"status": "experimental",
+							"id": "v3.0",
+							"links": [
+								{ "href": "%s", "rel": "self" }
+							]
+						},
+						{
+							"status": "stable",
+							"id": "v2.0",
+							"links": [
+								{ "href": "%s", "rel": "self" }
+							]
+						}
+					]
+				}
+			}
+		`, th.Endpoint()+"v3/", th.Endpoint()+"v2.0/")
+	})
+
+	th.Mux.HandleFunc("/v2.0/tokens", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, `
+			{
+				"access": {
+					"token": {
+						"id": "01234567890",
+						"expires": "2014-10-01T10:00:00.000000Z"
+					},
+					"serviceCatalog": [
+						{
+							"name": "Cloud Servers",
+							"type": "compute",
+							"endpoints": [
+								{
+									"tenantId": "t1000",
+									"publicURL": "https://compute.north.host.com/v1/t1000",
+									"internalURL": "https://compute.north.internal/v1/t1000",
+									"region": "North",
+									"versionId": "1",
+									"versionInfo": "https://compute.north.host.com/v1/",
+									"versionList": "https://compute.north.host.com/"
+								},
+								{
+									"tenantId": "t1000",
+									"publicURL": "https://compute.north.host.com/v1.1/t1000",
+									"internalURL": "https://compute.north.internal/v1.1/t1000",
+									"region": "North",
+									"versionId": "1.1",
+									"versionInfo": "https://compute.north.host.com/v1.1/",
+									"versionList": "https://compute.north.host.com/"
+								}
+							],
+							"endpoints_links": []
+						},
+						{
+							"name": "Cloud Files",
+							"type": "object-store",
+							"endpoints": [
+								{
+									"tenantId": "t1000",
+									"publicURL": "https://storage.north.host.com/v1/t1000",
+									"internalURL": "https://storage.north.internal/v1/t1000",
+									"region": "North",
+									"versionId": "1",
+									"versionInfo": "https://storage.north.host.com/v1/",
+									"versionList": "https://storage.north.host.com/"
+								},
+								{
+									"tenantId": "t1000",
+									"publicURL": "https://storage.south.host.com/v1/t1000",
+									"internalURL": "https://storage.south.internal/v1/t1000",
+									"region": "South",
+									"versionId": "1",
+									"versionInfo": "https://storage.south.host.com/v1/",
+									"versionList": "https://storage.south.host.com/"
+								}
+							]
+						}
+					]
+				}
+			}
+		`)
+	})
+
+	options := gophercloud.AuthOptions{
+		Username:         "me",
+		Password:         "secret",
+		IdentityEndpoint: th.Endpoint(),
+	}
+	client, err := openstack.AuthenticatedClient(options)
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, "01234567890", client.TokenID)
+}
diff --git a/openstack/testing/doc.go b/openstack/testing/doc.go
new file mode 100644
index 0000000..7603f83
--- /dev/null
+++ b/openstack/testing/doc.go
@@ -0,0 +1 @@
+package testing
diff --git a/openstack/testing/endpoint_location_test.go b/openstack/testing/endpoint_location_test.go
new file mode 100644
index 0000000..ea7bdd2
--- /dev/null
+++ b/openstack/testing/endpoint_location_test.go
@@ -0,0 +1,231 @@
+package testing
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/openstack"
+	tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
+	tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
+	th "github.com/gophercloud/gophercloud/testhelper"
+)
+
+// Service catalog fixtures take too much vertical space!
+var catalog2 = tokens2.ServiceCatalog{
+	Entries: []tokens2.CatalogEntry{
+		tokens2.CatalogEntry{
+			Type: "same",
+			Name: "same",
+			Endpoints: []tokens2.Endpoint{
+				tokens2.Endpoint{
+					Region:      "same",
+					PublicURL:   "https://public.correct.com/",
+					InternalURL: "https://internal.correct.com/",
+					AdminURL:    "https://admin.correct.com/",
+				},
+				tokens2.Endpoint{
+					Region:    "different",
+					PublicURL: "https://badregion.com/",
+				},
+			},
+		},
+		tokens2.CatalogEntry{
+			Type: "same",
+			Name: "different",
+			Endpoints: []tokens2.Endpoint{
+				tokens2.Endpoint{
+					Region:    "same",
+					PublicURL: "https://badname.com/",
+				},
+				tokens2.Endpoint{
+					Region:    "different",
+					PublicURL: "https://badname.com/+badregion",
+				},
+			},
+		},
+		tokens2.CatalogEntry{
+			Type: "different",
+			Name: "different",
+			Endpoints: []tokens2.Endpoint{
+				tokens2.Endpoint{
+					Region:    "same",
+					PublicURL: "https://badtype.com/+badname",
+				},
+				tokens2.Endpoint{
+					Region:    "different",
+					PublicURL: "https://badtype.com/+badregion+badname",
+				},
+			},
+		},
+	},
+}
+
+func TestV2EndpointExact(t *testing.T) {
+	expectedURLs := map[gophercloud.Availability]string{
+		gophercloud.AvailabilityPublic:   "https://public.correct.com/",
+		gophercloud.AvailabilityAdmin:    "https://admin.correct.com/",
+		gophercloud.AvailabilityInternal: "https://internal.correct.com/",
+	}
+
+	for availability, expected := range expectedURLs {
+		actual, err := openstack.V2EndpointURL(&catalog2, gophercloud.EndpointOpts{
+			Type:         "same",
+			Name:         "same",
+			Region:       "same",
+			Availability: availability,
+		})
+		th.AssertNoErr(t, err)
+		th.CheckEquals(t, expected, actual)
+	}
+}
+
+func TestV2EndpointNone(t *testing.T) {
+	_, actual := openstack.V2EndpointURL(&catalog2, gophercloud.EndpointOpts{
+		Type:         "nope",
+		Availability: gophercloud.AvailabilityPublic,
+	})
+	expected := &gophercloud.ErrEndpointNotFound{}
+	th.CheckEquals(t, expected.Error(), actual.Error())
+}
+
+func TestV2EndpointMultiple(t *testing.T) {
+	_, err := openstack.V2EndpointURL(&catalog2, gophercloud.EndpointOpts{
+		Type:         "same",
+		Region:       "same",
+		Availability: gophercloud.AvailabilityPublic,
+	})
+	if !strings.HasPrefix(err.Error(), "Discovered 2 matching endpoints:") {
+		t.Errorf("Received unexpected error: %v", err)
+	}
+}
+
+func TestV2EndpointBadAvailability(t *testing.T) {
+	_, err := openstack.V2EndpointURL(&catalog2, gophercloud.EndpointOpts{
+		Type:         "same",
+		Name:         "same",
+		Region:       "same",
+		Availability: "wat",
+	})
+	th.CheckEquals(t, "Unexpected availability in endpoint query: wat", err.Error())
+}
+
+var catalog3 = tokens3.ServiceCatalog{
+	Entries: []tokens3.CatalogEntry{
+		tokens3.CatalogEntry{
+			Type: "same",
+			Name: "same",
+			Endpoints: []tokens3.Endpoint{
+				tokens3.Endpoint{
+					ID:        "1",
+					Region:    "same",
+					Interface: "public",
+					URL:       "https://public.correct.com/",
+				},
+				tokens3.Endpoint{
+					ID:        "2",
+					Region:    "same",
+					Interface: "admin",
+					URL:       "https://admin.correct.com/",
+				},
+				tokens3.Endpoint{
+					ID:        "3",
+					Region:    "same",
+					Interface: "internal",
+					URL:       "https://internal.correct.com/",
+				},
+				tokens3.Endpoint{
+					ID:        "4",
+					Region:    "different",
+					Interface: "public",
+					URL:       "https://badregion.com/",
+				},
+			},
+		},
+		tokens3.CatalogEntry{
+			Type: "same",
+			Name: "different",
+			Endpoints: []tokens3.Endpoint{
+				tokens3.Endpoint{
+					ID:        "5",
+					Region:    "same",
+					Interface: "public",
+					URL:       "https://badname.com/",
+				},
+				tokens3.Endpoint{
+					ID:        "6",
+					Region:    "different",
+					Interface: "public",
+					URL:       "https://badname.com/+badregion",
+				},
+			},
+		},
+		tokens3.CatalogEntry{
+			Type: "different",
+			Name: "different",
+			Endpoints: []tokens3.Endpoint{
+				tokens3.Endpoint{
+					ID:        "7",
+					Region:    "same",
+					Interface: "public",
+					URL:       "https://badtype.com/+badname",
+				},
+				tokens3.Endpoint{
+					ID:        "8",
+					Region:    "different",
+					Interface: "public",
+					URL:       "https://badtype.com/+badregion+badname",
+				},
+			},
+		},
+	},
+}
+
+func TestV3EndpointExact(t *testing.T) {
+	expectedURLs := map[gophercloud.Availability]string{
+		gophercloud.AvailabilityPublic:   "https://public.correct.com/",
+		gophercloud.AvailabilityAdmin:    "https://admin.correct.com/",
+		gophercloud.AvailabilityInternal: "https://internal.correct.com/",
+	}
+
+	for availability, expected := range expectedURLs {
+		actual, err := openstack.V3EndpointURL(&catalog3, gophercloud.EndpointOpts{
+			Type:         "same",
+			Name:         "same",
+			Region:       "same",
+			Availability: availability,
+		})
+		th.AssertNoErr(t, err)
+		th.CheckEquals(t, expected, actual)
+	}
+}
+
+func TestV3EndpointNone(t *testing.T) {
+	_, actual := openstack.V3EndpointURL(&catalog3, gophercloud.EndpointOpts{
+		Type:         "nope",
+		Availability: gophercloud.AvailabilityPublic,
+	})
+	expected := &gophercloud.ErrEndpointNotFound{}
+	th.CheckEquals(t, expected.Error(), actual.Error())
+}
+
+func TestV3EndpointMultiple(t *testing.T) {
+	_, err := openstack.V3EndpointURL(&catalog3, gophercloud.EndpointOpts{
+		Type:         "same",
+		Region:       "same",
+		Availability: gophercloud.AvailabilityPublic,
+	})
+	if !strings.HasPrefix(err.Error(), "Discovered 2 matching endpoints:") {
+		t.Errorf("Received unexpected error: %v", err)
+	}
+}
+
+func TestV3EndpointBadAvailability(t *testing.T) {
+	_, err := openstack.V3EndpointURL(&catalog3, gophercloud.EndpointOpts{
+		Type:         "same",
+		Name:         "same",
+		Region:       "same",
+		Availability: "wat",
+	})
+	th.CheckEquals(t, "Unexpected availability in endpoint query: wat", err.Error())
+}