restic/vendor/github.com/ncw/swift/swift_internal_test.go
2017-09-13 14:09:48 +02:00

629 lines
15 KiB
Go

// This tests the swift package internals
//
// It does not require access to a swift server
//
// FIXME need to add more tests and to check URLs and parameters
package swift
import (
"fmt"
"io"
"net"
"net/http"
"os"
"reflect"
"testing"
"time"
)
const (
TEST_ADDRESS = "localhost:5324"
AUTH_URL = "http://" + TEST_ADDRESS + "/v1.0"
PROXY_URL = "http://" + TEST_ADDRESS + "/proxy"
USERNAME = "test"
APIKEY = "apikey"
AUTH_TOKEN = "token"
)
// Globals
var (
server *SwiftServer
c *Connection
)
// SwiftServer implements a test swift server
type SwiftServer struct {
t *testing.T
checks []*Check
}
// Used to check and reply to http transactions
type Check struct {
in Headers
out Headers
rx *string
tx *string
err *Error
url *string
}
// Add a in check
func (check *Check) In(in Headers) *Check {
check.in = in
return check
}
// Add an out check
func (check *Check) Out(out Headers) *Check {
check.out = out
return check
}
// Add an Error check
func (check *Check) Error(StatusCode int, Text string) *Check {
check.err = newError(StatusCode, Text)
return check
}
// Add a rx check
func (check *Check) Rx(rx string) *Check {
check.rx = &rx
return check
}
// Add an tx check
func (check *Check) Tx(tx string) *Check {
check.tx = &tx
return check
}
// Add an URL check
func (check *Check) Url(url string) *Check {
check.url = &url
return check
}
// Add a check
func (s *SwiftServer) AddCheck(t *testing.T) *Check {
server.t = t
check := &Check{
in: Headers{},
out: Headers{},
err: nil,
}
s.checks = append(s.checks, check)
return check
}
// Responds to a request
func (s *SwiftServer) Respond(w http.ResponseWriter, r *http.Request) {
if len(s.checks) < 1 {
s.t.Fatal("Unexpected http transaction")
}
check := s.checks[0]
s.checks = s.checks[1:]
// Check URL
if check.url != nil && *check.url != r.URL.String() {
s.t.Errorf("Expecting URL %q but got %q", *check.url, r.URL)
}
// Check headers
for k, v := range check.in {
actual := r.Header.Get(k)
if actual != v {
s.t.Errorf("Expecting header %q=%q but got %q", k, v, actual)
}
}
// Write output headers
h := w.Header()
for k, v := range check.out {
h.Set(k, v)
}
// Return an error if required
if check.err != nil {
http.Error(w, check.err.Text, check.err.StatusCode)
} else {
if check.tx != nil {
_, err := w.Write([]byte(*check.tx))
if err != nil {
s.t.Error("Write failed", err)
}
}
}
}
// Checks to see all responses are used up
func (s *SwiftServer) Finished() {
if len(s.checks) > 0 {
s.t.Error("Unused checks", s.checks)
}
}
func handle(w http.ResponseWriter, r *http.Request) {
// out, _ := httputil.DumpRequest(r, true)
// os.Stdout.Write(out)
server.Respond(w, r)
}
func NewSwiftServer() *SwiftServer {
server := &SwiftServer{}
http.HandleFunc("/", handle)
go http.ListenAndServe(TEST_ADDRESS, nil)
fmt.Print("Waiting for server to start ")
for {
fmt.Print(".")
conn, err := net.Dial("tcp", TEST_ADDRESS)
if err == nil {
conn.Close()
fmt.Println(" Started")
break
}
}
return server
}
func init() {
server = NewSwiftServer()
c = &Connection{
UserName: USERNAME,
ApiKey: APIKEY,
AuthUrl: AUTH_URL,
}
}
// Check the error is a swift error
func checkError(t *testing.T, err error, StatusCode int, Text string) {
if err == nil {
t.Fatal("No error returned")
}
err2, ok := err.(*Error)
if !ok {
t.Fatal("Bad error type")
}
if err2.StatusCode != StatusCode {
t.Fatalf("Bad status code, expecting %d got %d", StatusCode, err2.StatusCode)
}
if err2.Text != Text {
t.Fatalf("Bad error string, expecting %q got %q", Text, err2.Text)
}
}
// FIXME copied from swift_test.go
func compareMaps(t *testing.T, a, b map[string]string) {
if len(a) != len(b) {
t.Error("Maps different sizes", a, b)
}
for ka, va := range a {
if vb, ok := b[ka]; !ok || va != vb {
t.Error("Difference in key", ka, va, b[ka])
}
}
for kb, vb := range b {
if va, ok := a[kb]; !ok || vb != va {
t.Error("Difference in key", kb, vb, a[kb])
}
}
}
func TestInternalError(t *testing.T) {
e := newError(404, "Not Found!")
if e.StatusCode != 404 || e.Text != "Not Found!" {
t.Fatal("Bad error")
}
if e.Error() != "Not Found!" {
t.Fatal("Bad error")
}
}
func testCheckClose(rd io.ReadCloser, e error) (err error) {
err = e
defer checkClose(rd, &err)
return
}
// Make a closer which returns the error of our choice
type myCloser struct {
err error
}
func (c *myCloser) Read([]byte) (int, error) {
return 0, io.EOF
}
func (c *myCloser) Close() error {
return c.err
}
func TestInternalCheckClose(t *testing.T) {
if testCheckClose(&myCloser{nil}, nil) != nil {
t.Fatal("bad 1")
}
if testCheckClose(&myCloser{nil}, ObjectCorrupted) != ObjectCorrupted {
t.Fatal("bad 2")
}
if testCheckClose(&myCloser{ObjectNotFound}, nil) != ObjectNotFound {
t.Fatal("bad 3")
}
if testCheckClose(&myCloser{ObjectNotFound}, ObjectCorrupted) != ObjectCorrupted {
t.Fatal("bad 4")
}
}
func TestInternalParseHeaders(t *testing.T) {
resp := &http.Response{StatusCode: 200}
if c.parseHeaders(resp, nil) != nil {
t.Error("Bad 1")
}
if c.parseHeaders(resp, authErrorMap) != nil {
t.Error("Bad 1")
}
resp = &http.Response{StatusCode: 299}
if c.parseHeaders(resp, nil) != nil {
t.Error("Bad 1")
}
resp = &http.Response{StatusCode: 199, Status: "BOOM"}
checkError(t, c.parseHeaders(resp, nil), 199, "HTTP Error: 199: BOOM")
resp = &http.Response{StatusCode: 300, Status: "BOOM"}
checkError(t, c.parseHeaders(resp, nil), 300, "HTTP Error: 300: BOOM")
resp = &http.Response{StatusCode: 404, Status: "BOOM"}
checkError(t, c.parseHeaders(resp, nil), 404, "HTTP Error: 404: BOOM")
if c.parseHeaders(resp, ContainerErrorMap) != ContainerNotFound {
t.Error("Bad 1")
}
if c.parseHeaders(resp, objectErrorMap) != ObjectNotFound {
t.Error("Bad 1")
}
}
func TestInternalReadHeaders(t *testing.T) {
resp := &http.Response{Header: http.Header{}}
compareMaps(t, readHeaders(resp), Headers{})
resp = &http.Response{Header: http.Header{
"one": []string{"1"},
"two": []string{"2"},
}}
compareMaps(t, readHeaders(resp), Headers{"one": "1", "two": "2"})
// FIXME this outputs a log which we should test and check
resp = &http.Response{Header: http.Header{
"one": []string{"1", "11", "111"},
"two": []string{"2"},
}}
compareMaps(t, readHeaders(resp), Headers{"one": "1", "two": "2"})
}
func TestInternalStorage(t *testing.T) {
// FIXME
}
// ------------------------------------------------------------
func TestInternalAuthenticate(t *testing.T) {
server.AddCheck(t).In(Headers{
"User-Agent": DefaultUserAgent,
"X-Auth-Key": APIKEY,
"X-Auth-User": USERNAME,
}).Out(Headers{
"X-Storage-Url": PROXY_URL,
"X-Auth-Token": AUTH_TOKEN,
}).Url("/v1.0")
defer server.Finished()
err := c.Authenticate()
if err != nil {
t.Fatal(err)
}
if c.StorageUrl != PROXY_URL {
t.Error("Bad storage url")
}
if c.AuthToken != AUTH_TOKEN {
t.Error("Bad auth token")
}
if !c.Authenticated() {
t.Error("Didn't authenticate")
}
}
func TestInternalAuthenticateDenied(t *testing.T) {
server.AddCheck(t).Error(400, "Bad request")
server.AddCheck(t).Error(401, "DENIED")
defer server.Finished()
c.UnAuthenticate()
err := c.Authenticate()
if err != AuthorizationFailed {
t.Fatal("Expecting AuthorizationFailed", err)
}
// FIXME
// if c.Authenticated() {
// t.Fatal("Expecting not authenticated")
// }
}
func TestInternalAuthenticateBad(t *testing.T) {
server.AddCheck(t).Out(Headers{
"X-Storage-Url": PROXY_URL,
})
defer server.Finished()
err := c.Authenticate()
checkError(t, err, 0, "Response didn't have storage url and auth token")
if c.Authenticated() {
t.Fatal("Expecting not authenticated")
}
server.AddCheck(t).Out(Headers{
"X-Auth-Token": AUTH_TOKEN,
})
err = c.Authenticate()
checkError(t, err, 0, "Response didn't have storage url and auth token")
if c.Authenticated() {
t.Fatal("Expecting not authenticated")
}
server.AddCheck(t)
err = c.Authenticate()
checkError(t, err, 0, "Response didn't have storage url and auth token")
if c.Authenticated() {
t.Fatal("Expecting not authenticated")
}
server.AddCheck(t).Out(Headers{
"X-Storage-Url": PROXY_URL,
"X-Auth-Token": AUTH_TOKEN,
})
err = c.Authenticate()
if err != nil {
t.Fatal(err)
}
if !c.Authenticated() {
t.Fatal("Expecting authenticated")
}
}
func testContainerNames(t *testing.T, rx string, expected []string) {
server.AddCheck(t).In(Headers{
"User-Agent": DefaultUserAgent,
"X-Auth-Token": AUTH_TOKEN,
}).Tx(rx).Url("/proxy")
containers, err := c.ContainerNames(nil)
if err != nil {
t.Fatal(err)
}
if len(containers) != len(expected) {
t.Fatal("Wrong number of containers", len(containers), rx, len(expected), expected)
}
for i := range containers {
if containers[i] != expected[i] {
t.Error("Bad container", containers[i], expected[i])
}
}
}
func TestInternalContainerNames(t *testing.T) {
defer server.Finished()
testContainerNames(t, "", []string{})
testContainerNames(t, "one", []string{"one"})
testContainerNames(t, "one\n", []string{"one"})
testContainerNames(t, "one\ntwo\nthree\n", []string{"one", "two", "three"})
}
func TestInternalObjectPutBytes(t *testing.T) {
server.AddCheck(t).In(Headers{
"User-Agent": DefaultUserAgent,
"X-Auth-Token": AUTH_TOKEN,
"Content-Length": "5",
"Content-Type": "text/plain",
}).Rx("12345")
defer server.Finished()
c.ObjectPutBytes("container", "object", []byte{'1', '2', '3', '4', '5'}, "text/plain")
}
func TestInternalObjectPutString(t *testing.T) {
server.AddCheck(t).In(Headers{
"User-Agent": DefaultUserAgent,
"X-Auth-Token": AUTH_TOKEN,
"Content-Length": "5",
"Content-Type": "text/plain",
}).Rx("12345")
defer server.Finished()
c.ObjectPutString("container", "object", "12345", "text/plain")
}
func TestSetFromEnv(t *testing.T) {
// String
s := ""
os.Setenv("POTATO", "")
err := setFromEnv(&s, "POTATO")
if err != nil {
t.Fatal(err)
}
os.Setenv("POTATO", "this is a test")
err = setFromEnv(&s, "POTATO")
if err != nil {
t.Fatal(err)
}
if s != "this is a test" {
t.Fatal("incorrect", s)
}
os.Setenv("POTATO", "new")
err = setFromEnv(&s, "POTATO")
if err != nil {
t.Fatal(err)
}
if s != "this is a test" {
t.Fatal("was reset when it shouldn't have been")
}
// Integer
i := 0
os.Setenv("POTATO", "42")
err = setFromEnv(&i, "POTATO")
if err != nil {
t.Fatal(err)
}
if i != 42 {
t.Fatal("incorrect", i)
}
os.Setenv("POTATO", "43")
err = setFromEnv(&i, "POTATO")
if err != nil {
t.Fatal(err)
}
if i != 42 {
t.Fatal("was reset when it shouldn't have been")
}
i = 0
os.Setenv("POTATO", "not a number")
err = setFromEnv(&i, "POTATO")
if err == nil {
t.Fatal("expecting error but didn't get one")
}
// bool
var b bool
os.Setenv("POTATO", "1")
err = setFromEnv(&b, "POTATO")
if err != nil {
t.Fatal(err)
}
if b != true {
t.Fatal("incorrect", b)
}
// time.Duration
var dt time.Duration
os.Setenv("POTATO", "5s")
err = setFromEnv(&dt, "POTATO")
if err != nil {
t.Fatal(err)
}
if dt != 5*time.Second {
t.Fatal("incorrect", dt)
}
// EndpointType
var e EndpointType
os.Setenv("POTATO", "internal")
err = setFromEnv(&e, "POTATO")
if err != nil {
t.Fatal(err)
}
if e != EndpointType("internal") {
t.Fatal("incorrect", e)
}
// Unknown
var unknown struct{}
err = setFromEnv(&unknown, "POTATO")
if err == nil {
t.Fatal("expecting error")
}
os.Setenv("POTATO", "")
}
func TestApplyEnvironment(t *testing.T) {
// We've tested all the setting logic above, so just do a quick test here
c := new(Connection)
os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "100s")
err := c.ApplyEnvironment()
if err != nil {
t.Fatal(err)
}
if c.ConnectTimeout != 100*time.Second {
t.Fatal("timeout incorrect", c.ConnectTimeout)
}
c.ConnectTimeout = 0
os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "parse error")
err = c.ApplyEnvironment()
if err == nil {
t.Fatal("expecting error")
}
if c.ConnectTimeout != 0 {
t.Fatal("timeout incorrect", c.ConnectTimeout)
}
os.Setenv("GOSWIFT_CONNECT_TIMEOUT", "")
}
func TestApplyEnvironmentAll(t *testing.T) {
// we do this in two phases because some of the variable set the same thing
for phase := 1; phase <= 2; phase++ {
c := new(Connection)
items := []struct {
phase int
result interface{}
name string
value string
want interface{}
oldValue string
}{
// Copied and amended from ApplyEnvironment
// Environment variables - keep in same order as Connection
{1, &c.Domain, "OS_USER_DOMAIN_NAME", "os_user_domain_name", "os_user_domain_name", ""},
{1, &c.DomainId, "OS_USER_DOMAIN_ID", "os_user_domain_id", "os_user_domain_id", ""},
{1, &c.UserName, "OS_USERNAME", "os_username", "os_username", ""},
{1, &c.UserId, "OS_USER_ID", "os_user_id", "os_user_id", ""},
{1, &c.ApiKey, "OS_PASSWORD", "os_password", "os_password", ""},
{1, &c.AuthUrl, "OS_AUTH_URL", "os_auth_url", "os_auth_url", ""},
{1, &c.Retries, "GOSWIFT_RETRIES", "4", 4, ""},
{1, &c.UserAgent, "GOSWIFT_USER_AGENT", "goswift_user_agent", "goswift_user_agent", ""},
{1, &c.ConnectTimeout, "GOSWIFT_CONNECT_TIMEOUT", "98s", 98 * time.Second, ""},
{1, &c.Timeout, "GOSWIFT_TIMEOUT", "99s", 99 * time.Second, ""},
{1, &c.Region, "OS_REGION_NAME", "os_region_name", "os_region_name", ""},
{1, &c.AuthVersion, "ST_AUTH_VERSION", "3", 3, ""},
{1, &c.Internal, "GOSWIFT_INTERNAL", "true", true, ""},
{1, &c.Tenant, "OS_TENANT_NAME", "os_tenant_name", "os_tenant_name", ""},
{2, &c.Tenant, "OS_PROJECT_NAME", "os_project_name", "os_project_name", ""},
{1, &c.TenantId, "OS_TENANT_ID", "os_tenant_id", "os_tenant_id", ""},
{1, &c.EndpointType, "OS_ENDPOINT_TYPE", "internal", EndpointTypeInternal, ""},
{1, &c.TenantDomain, "OS_PROJECT_DOMAIN_NAME", "os_project_domain_name", "os_project_domain_name", ""},
{1, &c.TenantDomainId, "OS_PROJECT_DOMAIN_ID", "os_project_domain_id", "os_project_domain_id", ""},
{1, &c.TrustId, "OS_TRUST_ID", "os_trust_id", "os_trust_id", ""},
{1, &c.StorageUrl, "OS_STORAGE_URL", "os_storage_url", "os_storage_url", ""},
{1, &c.AuthToken, "OS_AUTH_TOKEN", "os_auth_token", "os_auth_token", ""},
// v1 auth alternatives
{2, &c.ApiKey, "ST_KEY", "st_key", "st_key", ""},
{2, &c.UserName, "ST_USER", "st_user", "st_user", ""},
{2, &c.AuthUrl, "ST_AUTH", "st_auth", "st_auth", ""},
}
for i := range items {
item := &items[i]
if item.phase == phase {
item.oldValue = os.Getenv(item.name) // save old value
os.Setenv(item.name, item.value) // set new value
}
}
err := c.ApplyEnvironment()
if err != nil {
t.Fatalf("unexpected error %v", err)
}
for i := range items {
item := &items[i]
if item.phase == phase {
got := reflect.Indirect(reflect.ValueOf(item.result)).Interface()
if !reflect.DeepEqual(item.want, got) {
t.Errorf("%s: %v != %v", item.name, item.want, got)
}
os.Setenv(item.name, item.oldValue) // restore old value
}
}
}
}