aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/account_test.go92
-rw-r--r--tests/backend_test.go43
-rw-r--r--tests/container_iterator_test.go187
-rw-r--r--tests/container_test.go85
-rw-r--r--tests/field_test.go167
-rw-r--r--tests/headers_test.go47
-rw-r--r--tests/object_iterator_test.go188
-rw-r--r--tests/object_test.go195
-rw-r--r--tests/shared_test.go216
9 files changed, 1220 insertions, 0 deletions
diff --git a/tests/account_test.go b/tests/account_test.go
new file mode 100644
index 0000000..310d4ce
--- /dev/null
+++ b/tests/account_test.go
@@ -0,0 +1,92 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "testing"
+
+ "github.com/majewsky/schwift"
+)
+
+func TestAccountBasic(t *testing.T) {
+ testWithAccount(t, func(a *schwift.Account) {
+ hdr, err := a.Headers()
+ if !expectSuccess(t, err) {
+ t.FailNow()
+ }
+ //There are not a lot of things we can test here (besides testing that
+ //Headers() does not fail, i.e. everything parses correctly), but
+ //Content-Type is going to be text/plain because GET on an account lists
+ //the container names as plain text.
+ expectString(t, hdr.Get("Content-Type"), "text/plain; charset=utf-8")
+ })
+}
+
+func TestAccountMetadata(t *testing.T) {
+ testWithAccount(t, func(a *schwift.Account) {
+ //test creating some metadata
+ hdr := make(schwift.AccountHeaders)
+ hdr.Metadata().Set("schwift-test1", "first")
+ hdr.Metadata().Set("schwift-test2", "second")
+ err := a.Update(hdr, nil)
+ if !expectSuccess(t, err) {
+ t.FailNow()
+ }
+
+ hdr, err = a.Headers()
+ if !expectSuccess(t, err) {
+ t.FailNow()
+ }
+ expectString(t, hdr.Metadata().Get("schwift-test1"), "first")
+ expectString(t, hdr.Metadata().Get("schwift-test2"), "second")
+
+ //test deleting some metadata
+ hdr = make(schwift.AccountHeaders)
+ hdr.Metadata().Clear("schwift-test1")
+ err = a.Update(hdr, nil)
+ if !expectSuccess(t, err) {
+ t.FailNow()
+ }
+
+ hdr, err = a.Headers()
+ if !expectSuccess(t, err) {
+ t.FailNow()
+ }
+ expectString(t, hdr.Metadata().Get("schwift-test1"), "")
+ expectString(t, hdr.Metadata().Get("schwift-test2"), "second")
+
+ //test updating some metadata
+ hdr = make(schwift.AccountHeaders)
+ hdr.Metadata().Set("schwift-test1", "will not be set")
+ hdr.Metadata().Del("schwift-test1")
+ hdr.Metadata().Set("schwift-test2", "changed")
+ err = a.Update(hdr, nil)
+ if !expectSuccess(t, err) {
+ t.FailNow()
+ }
+
+ hdr, err = a.Headers()
+ if !expectSuccess(t, err) {
+ t.FailNow()
+ }
+ expectString(t, hdr.Metadata().Get("schwift-test1"), "")
+ expectString(t, hdr.Metadata().Get("schwift-test2"), "changed")
+
+ })
+}
diff --git a/tests/backend_test.go b/tests/backend_test.go
new file mode 100644
index 0000000..eca7232
--- /dev/null
+++ b/tests/backend_test.go
@@ -0,0 +1,43 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "net/http"
+
+ "github.com/majewsky/schwift"
+)
+
+type RequestCountingBackend struct {
+ Inner schwift.Backend
+ Count int
+}
+
+func (b *RequestCountingBackend) EndpointURL() string {
+ return b.Inner.EndpointURL()
+}
+
+func (b *RequestCountingBackend) Clone(newEndpointURL string) schwift.Backend {
+ return &RequestCountingBackend{Inner: b.Inner.Clone(newEndpointURL)}
+}
+
+func (b *RequestCountingBackend) Do(req *http.Request) (*http.Response, error) {
+ b.Count++
+ return b.Inner.Do(req)
+}
diff --git a/tests/container_iterator_test.go b/tests/container_iterator_test.go
new file mode 100644
index 0000000..b9813db
--- /dev/null
+++ b/tests/container_iterator_test.go
@@ -0,0 +1,187 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/majewsky/schwift"
+)
+
+func TestContainerIterator(t *testing.T) {
+ testWithAccount(t, func(a *schwift.Account) {
+ cname := func(idx int) string {
+ return fmt.Sprintf("schwift-test-listing%d", idx)
+ }
+
+ //create test containers that can be listed
+ for idx := 1; idx <= 4; idx++ {
+ _, err := a.Container(cname(idx)).EnsureExists()
+ expectSuccess(t, err)
+ }
+
+ //test iteration with empty last page
+ iter := a.Containers()
+ iter.Prefix = "schwift-test-listing"
+ cs, err := iter.NextPage(2)
+ expectSuccess(t, err)
+ expectContainerNames(t, cs, cname(1), cname(2))
+ cs, err = iter.NextPage(2)
+ expectSuccess(t, err)
+ expectContainerNames(t, cs, cname(3), cname(4))
+ cs, err = iter.NextPage(2)
+ expectSuccess(t, err)
+ expectContainerNames(t, cs)
+ cs, err = iter.NextPage(2)
+ expectSuccess(t, err)
+ expectContainerNames(t, cs)
+
+ //test iteration with partial last page
+ iter = a.Containers()
+ iter.Prefix = "schwift-test-listing"
+ cs, err = iter.NextPage(3)
+ expectSuccess(t, err)
+ expectContainerNames(t, cs, cname(1), cname(2), cname(3))
+ cs, err = iter.NextPage(3)
+ expectSuccess(t, err)
+ expectContainerNames(t, cs, cname(4))
+ cs, err = iter.NextPage(4)
+ expectSuccess(t, err)
+ expectContainerNames(t, cs)
+
+ //test detailed iteration
+ iter = a.Containers()
+ iter.Prefix = "schwift-test-listing"
+ cis, err := iter.NextPageDetailed(2)
+ expectSuccess(t, err)
+ expectContainerInfos(t, cis, cname(1), cname(2))
+ cis, err = iter.NextPageDetailed(3)
+ expectSuccess(t, err)
+ expectContainerInfos(t, cis, cname(3), cname(4))
+ cis, err = iter.NextPageDetailed(3)
+ expectSuccess(t, err)
+ expectContainerInfos(t, cis)
+ cis, err = iter.NextPageDetailed(3)
+ expectSuccess(t, err)
+ expectContainerInfos(t, cis)
+
+ //test Foreach
+ a.Invalidate()
+ iter = a.Containers()
+ iter.Prefix = "schwift-test-listing"
+ idx := 0
+ expectSuccess(t, iter.Foreach(func(c *schwift.Container) error {
+ idx++
+ expectString(t, c.Name(), cname(idx))
+ return nil
+ }))
+ expectInt(t, idx, 4)
+ expectAccountHeadersCached(t, a)
+
+ //test ForeachDetailed
+ a.Invalidate()
+ iter = a.Containers()
+ iter.Prefix = "schwift-test-listing"
+ idx = 0
+ expectSuccess(t, iter.ForeachDetailed(func(info schwift.ContainerInfo) error {
+ idx++
+ expectString(t, info.Container.Name(), cname(idx))
+ return nil
+ }))
+ expectInt(t, idx, 4)
+ expectAccountHeadersCached(t, a)
+
+ //test Collect
+ iter = a.Containers()
+ iter.Prefix = "schwift-test-listing"
+ cs, err = iter.Collect()
+ expectSuccess(t, err)
+ expectContainerNames(t, cs, cname(1), cname(2), cname(3), cname(4))
+
+ //test CollectDetailed
+ iter = a.Containers()
+ iter.Prefix = "schwift-test-listing"
+ cis, err = iter.CollectDetailed()
+ expectSuccess(t, err)
+ expectContainerInfos(t, cis, cname(1), cname(2), cname(3), cname(4))
+
+ //cleanup
+ iter = a.Containers()
+ iter.Prefix = "schwift-test-listing"
+ expectSuccess(t, iter.Foreach(func(c *schwift.Container) error {
+ return c.Delete(nil, nil)
+ }))
+ })
+}
+
+func expectAccountHeadersCached(t *testing.T, a *schwift.Account) {
+ requestCountBefore := a.Backend().(*RequestCountingBackend).Count
+ _, err := a.Headers()
+ expectSuccess(t, err)
+ requestCountAfter := a.Backend().(*RequestCountingBackend).Count
+
+ t.Helper()
+ if requestCountBefore != requestCountAfter {
+ t.Error("Account.Headers was expected to use cache, but issued HEAD request")
+ }
+}
+
+func expectContainerNames(t *testing.T, actualContainers []*schwift.Container, expectedNames ...string) {
+ t.Helper()
+ if len(actualContainers) != len(expectedNames) {
+ t.Errorf("expected %d containers, got %d containers",
+ len(expectedNames), len(actualContainers))
+ return
+ }
+ for idx, c := range actualContainers {
+ if c.Name() != expectedNames[idx] {
+ t.Errorf("expected containers[%d].Name() == %q, got %q",
+ idx, expectedNames[idx], c.Name())
+ }
+ }
+}
+
+func expectContainerInfos(t *testing.T, actualInfos []schwift.ContainerInfo, expectedNames ...string) {
+ t.Helper()
+ if len(actualInfos) != len(expectedNames) {
+ t.Errorf("expected %d containers, got %d containers",
+ len(expectedNames), len(actualInfos))
+ return
+ }
+ for idx, info := range actualInfos {
+ if info.Container.Name() != expectedNames[idx] {
+ t.Errorf("expected containers[%d].Name() == %q, got %q",
+ idx, expectedNames[idx], info.Container.Name())
+ }
+ //TODO: upload test object of defined size to the listed containers to
+ //check if this zero is not just the default value
+ if info.BytesUsed != 0 {
+ t.Errorf("expected containers[%d] bytesUsed == 0, got %d",
+ idx, info.BytesUsed)
+ }
+ if info.ObjectCount != 0 {
+ t.Errorf("expected containers[%d] objectCount == 0, got %d",
+ idx, info.ObjectCount)
+ }
+ if info.LastModified.IsZero() {
+ t.Errorf("containers[%d].LastModified is zero", idx)
+ }
+ }
+}
diff --git a/tests/container_test.go b/tests/container_test.go
new file mode 100644
index 0000000..5a0046b
--- /dev/null
+++ b/tests/container_test.go
@@ -0,0 +1,85 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/majewsky/schwift"
+)
+
+func TestContainerLifecycle(t *testing.T) {
+ testWithAccount(t, func(a *schwift.Account) {
+ containerName := getRandomName()
+ c := a.Container(containerName)
+
+ expectString(t, c.Name(), containerName)
+ if c.Account() != a {
+ t.Errorf("expected c.Account() = %#v, got %#v instead\n", a, c.Account())
+ }
+
+ exists, err := c.Exists()
+ expectSuccess(t, err)
+ expectBool(t, exists, false)
+
+ _, err = c.Headers()
+ expectError(t, err, "expected 204 response, got 404 instead")
+ expectBool(t, schwift.Is(err, http.StatusNotFound), true)
+ expectBool(t, schwift.Is(err, http.StatusNoContent), false)
+
+ //DELETE should be idempotent and not return success on non-existence, but
+ //OpenStack LOVES to be inconsistent with everything (including, notably, itself)
+ err = c.Delete(nil, nil)
+ expectError(t, err, "expected 204 response, got 404 instead: <html><h1>Not Found</h1><p>The resource could not be found.</p></html>")
+
+ err = c.Create(nil, nil)
+ expectSuccess(t, err)
+
+ exists, err = c.Exists()
+ expectSuccess(t, err)
+ expectBool(t, exists, true)
+
+ err = c.Delete(nil, nil)
+ expectSuccess(t, err)
+ })
+}
+
+func TestContainerUpdate(t *testing.T) {
+ testWithContainer(t, func(c *schwift.Container) {
+
+ hdr, err := c.Headers()
+ expectSuccess(t, err)
+ expectBool(t, hdr.ObjectCount().Exists(), true)
+ expectUint64(t, hdr.ObjectCount().Get(), 0)
+
+ hdr = make(schwift.ContainerHeaders)
+ hdr.ObjectCountQuota().Set(23)
+ hdr.BytesUsedQuota().Set(42)
+
+ err = c.Update(hdr, nil)
+ expectSuccess(t, err)
+
+ hdr, err = c.Headers()
+ expectSuccess(t, err)
+ expectUint64(t, hdr.BytesUsedQuota().Get(), 42)
+ expectUint64(t, hdr.ObjectCountQuota().Get(), 23)
+
+ })
+}
diff --git a/tests/field_test.go b/tests/field_test.go
new file mode 100644
index 0000000..f3c03ca
--- /dev/null
+++ b/tests/field_test.go
@@ -0,0 +1,167 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "net/http"
+ "strconv"
+ "testing"
+
+ "github.com/majewsky/schwift"
+)
+
+func TestFieldString(t *testing.T) {
+ hdr := make(schwift.AccountHeaders)
+ expectBool(t, hdr.TempURLKey().Exists(), false)
+ expectString(t, hdr.TempURLKey().Get(), "")
+ expectSuccess(t, hdr.Validate())
+
+ hdr["X-Account-Meta-Temp-Url-Key"] = ""
+ expectBool(t, hdr.TempURLKey().Exists(), false)
+ expectString(t, hdr.TempURLKey().Get(), "")
+ expectSuccess(t, hdr.Validate())
+
+ hdr["X-Account-Meta-Temp-Url-Key"] = "foo"
+ expectBool(t, hdr.TempURLKey().Exists(), true)
+ expectString(t, hdr.TempURLKey().Get(), "foo")
+ expectSuccess(t, hdr.Validate())
+
+ hdr.TempURLKey().Set("bar")
+ expectHeaders(t, hdr, map[string]string{
+ "X-Account-Meta-Temp-Url-Key": "bar",
+ })
+ hdr.TempURLKey().Clear()
+ expectHeaders(t, hdr, map[string]string{
+ "X-Account-Meta-Temp-Url-Key": "",
+ })
+ hdr.TempURLKey().Del()
+ expectHeaders(t, hdr, nil)
+ hdr.TempURLKey().Clear()
+ expectHeaders(t, hdr, map[string]string{
+ "X-Account-Meta-Temp-Url-Key": "",
+ })
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func TestFieldTimestamp(t *testing.T) {
+ testWithAccount(t, func(a *schwift.Account) {
+ hdr, err := a.Headers()
+ if !expectSuccess(t, err) {
+ return
+ }
+
+ expectBool(t, hdr.CreatedAt().Exists(), true)
+
+ actual := float64(hdr.CreatedAt().Get().UnixNano()) / 1e9
+ expected, _ := strconv.ParseFloat(hdr["X-Timestamp"], 64)
+ expectFloat64(t, actual, expected)
+ })
+
+ hdr := make(schwift.AccountHeaders)
+ expectBool(t, hdr.CreatedAt().Exists(), false)
+ expectBool(t, hdr.CreatedAt().Get().IsZero(), true)
+ expectSuccess(t, hdr.Validate())
+
+ hdr["X-Timestamp"] = "wtf"
+ expectBool(t, hdr.CreatedAt().Exists(), true)
+ expectBool(t, hdr.CreatedAt().Get().IsZero(), true)
+ expectError(t, hdr.Validate(), `Bad header X-Timestamp: strconv.ParseFloat: parsing "wtf": invalid syntax`)
+}
+
+func TestFieldHTTPTimestamp(t *testing.T) {
+ testWithContainer(t, func(c *schwift.Container) {
+ obj := c.Object("test")
+ err := obj.Upload(nil, nil, nil)
+ if !expectSuccess(t, err) {
+ return
+ }
+
+ hdr, err := obj.Headers()
+ if !expectSuccess(t, err) {
+ return
+ }
+ expectBool(t, hdr.UpdatedAt().Exists(), true)
+
+ actual := hdr.UpdatedAt().Get()
+ expected, _ := http.ParseTime(hdr.Get("Last-Modified"))
+ expectInt64(t, actual.Unix(), expected.Unix())
+ })
+
+ hdr := make(schwift.ObjectHeaders)
+ expectBool(t, hdr.UpdatedAt().Exists(), false)
+ expectBool(t, hdr.UpdatedAt().Get().IsZero(), true)
+ expectSuccess(t, hdr.Validate())
+
+ hdr["Last-Modified"] = "wtf"
+ expectBool(t, hdr.UpdatedAt().Exists(), true)
+ expectBool(t, hdr.UpdatedAt().Get().IsZero(), true)
+ expectError(t, hdr.Validate(), `Bad header Last-Modified: parsing time "wtf" as "Mon Jan _2 15:04:05 2006": cannot parse "wtf" as "Mon"`)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func TestFieldUint64(t *testing.T) {
+ hdr := make(schwift.AccountHeaders)
+ expectBool(t, hdr.BytesUsedQuota().Exists(), false)
+ expectUint64(t, hdr.BytesUsedQuota().Get(), 0)
+ expectSuccess(t, hdr.Validate())
+
+ hdr["X-Account-Meta-Quota-Bytes"] = "23"
+ expectBool(t, hdr.BytesUsedQuota().Exists(), true)
+ expectUint64(t, hdr.BytesUsedQuota().Get(), 23)
+ expectSuccess(t, hdr.Validate())
+
+ hdr["X-Account-Meta-Quota-Bytes"] = "-23"
+ expectBool(t, hdr.BytesUsedQuota().Exists(), true)
+ expectUint64(t, hdr.BytesUsedQuota().Get(), 0)
+ expectError(t, hdr.Validate(), `Bad header X-Account-Meta-Quota-Bytes: strconv.ParseUint: parsing "-23": invalid syntax`)
+
+ hdr.BytesUsedQuota().Set(9001)
+ expectHeaders(t, hdr, map[string]string{
+ "X-Account-Meta-Quota-Bytes": "9001",
+ })
+ hdr.BytesUsedQuota().Clear()
+ expectHeaders(t, hdr, map[string]string{
+ "X-Account-Meta-Quota-Bytes": "",
+ })
+ hdr.BytesUsedQuota().Del()
+ expectHeaders(t, hdr, nil)
+ hdr.BytesUsedQuota().Clear()
+ expectHeaders(t, hdr, map[string]string{
+ "X-Account-Meta-Quota-Bytes": "",
+ })
+}
+
+func TestFieldUint64Readonly(t *testing.T) {
+ hdr := make(schwift.AccountHeaders)
+ expectBool(t, hdr.BytesUsed().Exists(), false)
+ expectUint64(t, hdr.BytesUsed().Get(), 0)
+ expectSuccess(t, hdr.Validate())
+
+ hdr["X-Account-Bytes-Used"] = "23"
+ expectBool(t, hdr.BytesUsed().Exists(), true)
+ expectUint64(t, hdr.BytesUsed().Get(), 23)
+ expectSuccess(t, hdr.Validate())
+
+ hdr["X-Account-Bytes-Used"] = "-23"
+ expectBool(t, hdr.BytesUsed().Exists(), true)
+ expectUint64(t, hdr.BytesUsed().Get(), 0)
+ expectError(t, hdr.Validate(), `Bad header X-Account-Bytes-Used: strconv.ParseUint: parsing "-23": invalid syntax`)
+}
diff --git a/tests/headers_test.go b/tests/headers_test.go
new file mode 100644
index 0000000..8067ef4
--- /dev/null
+++ b/tests/headers_test.go
@@ -0,0 +1,47 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "testing"
+
+ "github.com/majewsky/schwift"
+)
+
+func TestParseAccountHeadersSuccess(t *testing.T) {
+ headers := schwift.AccountHeaders{
+ "X-Account-Bytes-Used": "1234",
+ "X-Account-Object-Count": "42",
+ "X-Account-Container-Count": "23",
+ "X-Account-Meta-Quota-Bytes": "1048576",
+ "X-Account-Meta-Foo": "bar",
+ }
+
+ expectSuccess(t, headers.Validate())
+ expectUint64(t, headers.BytesUsed().Get(), 1234)
+ expectUint64(t, headers.ContainerCount().Get(), 23)
+ expectUint64(t, headers.ObjectCount().Get(), 42)
+ expectUint64(t, headers.BytesUsedQuota().Get(), 1048576)
+
+ expectString(t, headers.Metadata().Get("foo"), "bar")
+ expectString(t, headers.Metadata().Get("Foo"), "bar")
+ expectString(t, headers.Metadata().Get("FOO"), "bar")
+}
+
+//TODO TestParseAccountHeadersError
diff --git a/tests/object_iterator_test.go b/tests/object_iterator_test.go
new file mode 100644
index 0000000..5112324
--- /dev/null
+++ b/tests/object_iterator_test.go
@@ -0,0 +1,188 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+
+ "github.com/majewsky/schwift"
+)
+
+var objectExampleContent = []byte(`{"message":"Hello World!"}`)
+var objectExampleContentEtag = etagOf(objectExampleContent)
+
+func TestObjectIterator(t *testing.T) {
+ testWithContainer(t, func(c *schwift.Container) {
+ oname := func(idx int) string {
+ return fmt.Sprintf("schwift-test-listing%d", idx)
+ }
+
+ //create test objects that can be listed
+ for idx := 1; idx <= 4; idx++ {
+ hdr := make(schwift.ObjectHeaders)
+ hdr.ContentType().Set("application/json")
+ err := c.Object(oname(idx)).Upload(bytes.NewReader(objectExampleContent), hdr, nil)
+ expectSuccess(t, err)
+ }
+
+ //test iteration with empty last page
+ iter := c.Objects()
+ iter.Prefix = "schwift-test-listing"
+ os, err := iter.NextPage(2)
+ expectSuccess(t, err)
+ expectObjectNames(t, os, oname(1), oname(2))
+ os, err = iter.NextPage(2)
+ expectSuccess(t, err)
+ expectObjectNames(t, os, oname(3), oname(4))
+ os, err = iter.NextPage(2)
+ expectSuccess(t, err)
+ expectObjectNames(t, os)
+ os, err = iter.NextPage(2)
+ expectSuccess(t, err)
+ expectObjectNames(t, os)
+
+ //test iteration with partial last page
+ iter = c.Objects()
+ iter.Prefix = "schwift-test-listing"
+ os, err = iter.NextPage(3)
+ expectSuccess(t, err)
+ expectObjectNames(t, os, oname(1), oname(2), oname(3))
+ os, err = iter.NextPage(3)
+ expectSuccess(t, err)
+ expectObjectNames(t, os, oname(4))
+ os, err = iter.NextPage(4)
+ expectSuccess(t, err)
+ expectObjectNames(t, os)
+
+ //test detailed iteration
+ iter = c.Objects()
+ iter.Prefix = "schwift-test-listing"
+ ois, err := iter.NextPageDetailed(2)
+ expectSuccess(t, err)
+ expectObjectInfos(t, ois, oname(1), oname(2))
+ ois, err = iter.NextPageDetailed(3)
+ expectSuccess(t, err)
+ expectObjectInfos(t, ois, oname(3), oname(4))
+ ois, err = iter.NextPageDetailed(3)
+ expectSuccess(t, err)
+ expectObjectInfos(t, ois)
+ ois, err = iter.NextPageDetailed(3)
+ expectSuccess(t, err)
+ expectObjectInfos(t, ois)
+
+ //test Foreach
+ c.Invalidate()
+ iter = c.Objects()
+ iter.Prefix = "schwift-test-listing"
+ idx := 0
+ expectSuccess(t, iter.Foreach(func(o *schwift.Object) error {
+ idx++
+ expectString(t, o.Name(), oname(idx))
+ return nil
+ }))
+ expectInt(t, idx, 4)
+ expectContainerHeadersCached(t, c)
+
+ //test ForeachDetailed
+ c.Invalidate()
+ iter = c.Objects()
+ iter.Prefix = "schwift-test-listing"
+ idx = 0
+ expectSuccess(t, iter.ForeachDetailed(func(info schwift.ObjectInfo) error {
+ idx++
+ expectString(t, info.Object.Name(), oname(idx))
+ return nil
+ }))
+ expectInt(t, idx, 4)
+ expectContainerHeadersCached(t, c)
+
+ //test Collect
+ iter = c.Objects()
+ iter.Prefix = "schwift-test-listing"
+ os, err = iter.Collect()
+ expectSuccess(t, err)
+ expectObjectNames(t, os, oname(1), oname(2), oname(3), oname(4))
+
+ //test CollectDetailed
+ iter = c.Objects()
+ iter.Prefix = "schwift-test-listing"
+ ois, err = iter.CollectDetailed()
+ expectSuccess(t, err)
+ expectObjectInfos(t, ois, oname(1), oname(2), oname(3), oname(4))
+ })
+}
+
+func expectContainerHeadersCached(t *testing.T, c *schwift.Container) {
+ requestCountBefore := c.Account().Backend().(*RequestCountingBackend).Count
+ _, err := c.Headers()
+ expectSuccess(t, err)
+ requestCountAfter := c.Account().Backend().(*RequestCountingBackend).Count
+
+ t.Helper()
+ if requestCountBefore != requestCountAfter {
+ t.Error("Container.Headers() was expected to use cache, but issued HEAD request")
+ }
+}
+
+func expectObjectNames(t *testing.T, actualObjects []*schwift.Object, expectedNames ...string) {
+ t.Helper()
+ if len(actualObjects) != len(expectedNames) {
+ t.Errorf("expected %d objects, got %d objects",
+ len(expectedNames), len(actualObjects))
+ return
+ }
+ for idx, c := range actualObjects {
+ if c.Name() != expectedNames[idx] {
+ t.Errorf("expected objects[%d].Name() == %q, got %q",
+ idx, expectedNames[idx], c.Name())
+ }
+ }
+}
+
+func expectObjectInfos(t *testing.T, actualInfos []schwift.ObjectInfo, expectedNames ...string) {
+ t.Helper()
+ if len(actualInfos) != len(expectedNames) {
+ t.Errorf("expected %d objects, got %d objects",
+ len(expectedNames), len(actualInfos))
+ return
+ }
+ for idx, info := range actualInfos {
+ if info.Object.Name() != expectedNames[idx] {
+ t.Errorf("expected objects[%d].Name() == %q, got %q",
+ idx, expectedNames[idx], info.Object.Name())
+ }
+ if info.SizeBytes != uint64(len(objectExampleContent)) {
+ t.Errorf("expected objects[%d] sizeBytes == %d, got %d",
+ idx, len(objectExampleContent), info.SizeBytes)
+ }
+ if info.ContentType != "application/json" {
+ t.Errorf(`expected objects[%d] contentType == "application/json", got %q`,
+ idx, info.ContentType)
+ }
+ if info.Etag != objectExampleContentEtag {
+ t.Errorf("expected objects[%d] etag == %q, got %q",
+ idx, objectExampleContentEtag, info.Etag)
+ }
+ if info.LastModified.IsZero() {
+ t.Errorf("objects[%d].LastModified is zero", idx)
+ }
+ }
+}
diff --git a/tests/object_test.go b/tests/object_test.go
new file mode 100644
index 0000000..740c3f2
--- /dev/null
+++ b/tests/object_test.go
@@ -0,0 +1,195 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "testing"
+
+ "github.com/majewsky/schwift"
+)
+
+func TestObjectLifecycle(t *testing.T) {
+ testWithContainer(t, func(c *schwift.Container) {
+ objectName := getRandomName()
+ o := c.Object(objectName)
+
+ expectString(t, o.Name(), objectName)
+ expectString(t, o.FullName(), c.Name()+"/"+objectName)
+ if o.Container() != c {
+ t.Errorf("expected o.Container() = %#v, got %#v instead\n", c, o.Container())
+ }
+
+ exists, err := o.Exists()
+ expectSuccess(t, err)
+ expectBool(t, exists, false)
+
+ _, err = o.Headers()
+ expectError(t, err, "expected 200 response, got 404 instead")
+ expectBool(t, schwift.Is(err, http.StatusNotFound), true)
+ expectBool(t, schwift.Is(err, http.StatusNoContent), false)
+
+ //DELETE should be idempotent and not return success on non-existence, but
+ //OpenStack LOVES to be inconsistent with everything (including, notably, itself)
+ err = o.Delete(nil, nil)
+ expectError(t, err, "expected 204 response, got 404 instead: <html><h1>Not Found</h1><p>The resource could not be found.</p></html>")
+
+ err = o.Upload(bytes.NewReader([]byte("test")), nil, nil)
+ expectSuccess(t, err)
+
+ exists, err = o.Exists()
+ expectSuccess(t, err)
+ expectBool(t, exists, true)
+
+ err = o.Delete(nil, nil)
+ expectSuccess(t, err)
+ })
+}
+
+func TestObjectUpload(t *testing.T) {
+ testWithContainer(t, func(c *schwift.Container) {
+ validateUploadedFile := func(obj *schwift.Object, expected []byte) {
+ str, err := obj.Download(nil, nil).AsString()
+ expectSuccess(t, err)
+ expectString(t, str, string(expected))
+ obj.Invalidate()
+ hdr, err := obj.Headers()
+ expectSuccess(t, err)
+ expectString(t, hdr.Etag().Get(), etagOf(expected))
+ }
+
+ //test upload with bytes.Reader
+ obj := c.Object("upload1")
+ err := obj.Upload(bytes.NewReader(objectExampleContent), nil, nil)
+ expectSuccess(t, err)
+ validateUploadedFile(obj, objectExampleContent)
+
+ //test upload with bytes.Buffer
+ obj = c.Object("upload2")
+ err = obj.Upload(bytes.NewBuffer(objectExampleContent), nil, nil)
+ expectSuccess(t, err)
+ validateUploadedFile(obj, objectExampleContent)
+
+ //test upload with opaque io.Reader
+ obj = c.Object("upload3")
+ err = obj.Upload(opaqueReader{bytes.NewReader(objectExampleContent)}, nil, nil)
+ expectSuccess(t, err)
+ validateUploadedFile(obj, objectExampleContent)
+
+ //test upload with io.Writer
+ obj = c.Object("upload4")
+ err = obj.UploadWithWriter(nil, nil, func(w io.Writer) error {
+ _, err := w.Write(objectExampleContent)
+ return err
+ })
+ expectSuccess(t, err)
+ validateUploadedFile(obj, objectExampleContent)
+
+ //test upload with empty reader (should create zero-byte-sized object)
+ obj = c.Object("upload5")
+ err = obj.Upload(eofReader{}, nil, nil)
+ expectSuccess(t, err)
+ validateUploadedFile(obj, nil)
+
+ //test upload without reader (should create zero-byte-sized object)
+ obj = c.Object("upload6")
+ err = obj.Upload(nil, nil, nil)
+ expectSuccess(t, err)
+ validateUploadedFile(obj, nil)
+ })
+}
+
+type eofReader struct{}
+
+func (r eofReader) Read([]byte) (int, error) {
+ return 0, io.EOF
+}
+
+type opaqueReader struct {
+ b *bytes.Reader
+}
+
+func (r opaqueReader) Read(buf []byte) (int, error) {
+ return r.b.Read(buf)
+}
+
+func TestObjectDownload(t *testing.T) {
+ testWithContainer(t, func(c *schwift.Container) {
+ //upload example object
+ obj := c.Object("example")
+ err := obj.Upload(bytes.NewReader(objectExampleContent), nil, nil)
+ expectSuccess(t, err)
+
+ //test download as string
+ str, err := obj.Download(nil, nil).AsString()
+ expectSuccess(t, err)
+ expectString(t, str, string(objectExampleContent))
+
+ //test download as byte slice
+ buf, err := obj.Download(nil, nil).AsByteSlice()
+ expectSuccess(t, err)
+ expectString(t, string(buf), string(objectExampleContent))
+
+ //test download as io.ReadCloser slice
+ reader, err := obj.Download(nil, nil).AsReadCloser()
+ expectSuccess(t, err)
+ buf = make([]byte, 4)
+ _, err = reader.Read(buf)
+ expectSuccess(t, err)
+ expectString(t, string(buf), string(objectExampleContent[0:4]))
+ _, err = reader.Read(buf)
+ expectSuccess(t, err)
+ expectString(t, string(buf), string(objectExampleContent[4:8]))
+ buf, err = ioutil.ReadAll(reader)
+ expectSuccess(t, err)
+ expectString(t, string(buf), string(objectExampleContent[8:]))
+ })
+}
+
+func TestObjectUpdate(t *testing.T) {
+ testWithContainer(t, func(c *schwift.Container) {
+ obj := c.Object("example")
+
+ //test that metadata update fails for non-existing object
+ newHeaders := make(schwift.ObjectHeaders)
+ newHeaders.ContentType().Set("application/json")
+ err := obj.Update(newHeaders, nil)
+ expectBool(t, schwift.Is(err, http.StatusNotFound), true)
+ expectError(t, err, "expected 202 response, got 404 instead: <html><h1>Not Found</h1><p>The resource could not be found.</p></html>")
+
+ //create object
+ err = obj.Upload(nil, nil, nil)
+ expectSuccess(t, err)
+
+ hdr, err := obj.Headers()
+ expectSuccess(t, err)
+ expectString(t, hdr.ContentType().Get(), "application/octet-stream")
+
+ //now the metadata update should work
+ err = obj.Update(newHeaders, nil)
+ expectSuccess(t, err)
+ obj.Invalidate()
+ hdr, err = obj.Headers()
+ expectSuccess(t, err)
+ expectString(t, hdr.ContentType().Get(), "application/json")
+ })
+}
diff --git a/tests/shared_test.go b/tests/shared_test.go
new file mode 100644
index 0000000..187d0b4
--- /dev/null
+++ b/tests/shared_test.go
@@ -0,0 +1,216 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+******************************************************************************/
+
+package tests
+
+import (
+ "crypto/md5"
+ "crypto/rand"
+ "encoding/hex"
+ "math"
+ "os"
+ "testing"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/openstack"
+ "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth"
+ "github.com/majewsky/schwift"
+ "github.com/majewsky/schwift/gopherschwift"
+)
+
+func testWithAccount(t *testing.T, testCode func(a *schwift.Account)) {
+ stAuth := os.Getenv("ST_AUTH")
+ stUser := os.Getenv("ST_USER")
+ stKey := os.Getenv("ST_KEY")
+ var client *gophercloud.ServiceClient
+
+ if stAuth == "" && stUser == "" && stKey == "" {
+ //option 1: Keystone authentication
+ authOptions, err := openstack.AuthOptionsFromEnv()
+ if err != nil {
+ t.Error("missing Swift credentials (need either ST_AUTH, ST_USER, ST_KEY or OS_* variables)")
+ t.Error("openstack.AuthOptionsFromEnv returned: " + err.Error())
+ return
+ }
+ provider, err := openstack.AuthenticatedClient(authOptions)
+ if err != nil {
+ t.Errorf("openstack.AuthenticatedClient returned: " + err.Error())
+ return
+ }
+ client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{})
+ if err != nil {
+ t.Errorf("openstack.NewObjectStorageV1 returned: " + err.Error())
+ return
+ }
+ } else {
+ //option 2: Swift authentication v1
+ provider, err := openstack.NewClient(stAuth)
+ if err != nil {
+ t.Errorf("openstack.NewClient returned: " + err.Error())
+ return
+ }
+ client, err = swauth.NewObjectStorageV1(provider, swauth.AuthOpts{User: stUser, Key: stKey})
+ if err != nil {
+ t.Errorf("swauth.NewObjectStorageV1 returned: " + err.Error())
+ return
+ }
+ }
+
+ account, err := gopherschwift.Wrap(client)
+ if err != nil {
+ t.Error(err.Error())
+ return
+ }
+ account, err = schwift.InitializeAccount(
+ &RequestCountingBackend{Inner: account.Backend()},
+ )
+ if err != nil {
+ t.Error(err.Error())
+ return
+ }
+ testCode(account)
+}
+
+func testWithContainer(t *testing.T, testCode func(c *schwift.Container)) {
+ testWithAccount(t, func(a *schwift.Account) {
+ containerName := getRandomName()
+ container, err := a.Container(containerName).EnsureExists()
+ expectSuccess(t, err)
+
+ testCode(container)
+
+ //cleanup
+ exists, err := container.Exists()
+ expectSuccess(t, err)
+ if exists {
+ expectSuccess(t, container.Objects().Foreach(func(o *schwift.Object) error {
+ return o.Delete(nil, nil)
+ }))
+ err = container.Delete(nil, nil)
+ expectSuccess(t, err)
+ }
+ })
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func etagOf(buf []byte) string {
+ hash := md5.Sum(buf)
+ return hex.EncodeToString(hash[:])
+}
+
+func getRandomName() string {
+ var buf [16]byte
+ _, err := rand.Read(buf[:])
+ if err != nil {
+ panic(err.Error())
+ }
+ return hex.EncodeToString(buf[:])
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+func expectBool(t *testing.T, actual, expected bool) {
+ t.Helper()
+ if actual != expected {
+ t.Errorf("expected value %#v, got %#v instead\n", expected, actual)
+ }
+}
+
+func expectFloat64(t *testing.T, actual, expected float64) {
+ t.Helper()
+ if math.Abs((actual-expected)/expected) > 1e-8 {
+ t.Errorf("expected value %g, got %g instead\n", expected, actual)
+ }
+}
+
+func expectInt(t *testing.T, actual, expected int) {
+ t.Helper()
+ if actual != expected {
+ t.Errorf("expected value %d, got %d instead\n", expected, actual)
+ }
+}
+
+func expectInt64(t *testing.T, actual, expected int64) {
+ t.Helper()
+ if actual != expected {
+ t.Errorf("expected value %d, got %d instead\n", expected, actual)
+ }
+}
+
+func expectUint64(t *testing.T, actual, expected uint64) {
+ t.Helper()
+ if actual != expected {
+ t.Errorf("expected value %d, got %d instead\n", expected, actual)
+ }
+}
+
+func expectString(t *testing.T, actual, expected string) {
+ t.Helper()
+ if actual != expected {
+ t.Errorf("expected value %q, got %q instead\n", expected, actual)
+ }
+}
+
+func expectError(t *testing.T, actual error, expected string) (ok bool) {
+ t.Helper()
+ if actual == nil {
+ t.Errorf("expected error %q, got no error\n", expected)
+ return false
+ }
+ if expected != actual.Error() {
+ t.Errorf("expected error %q, got %q instead\n", expected, actual.Error())
+ return false
+ }
+ return true
+}
+
+func expectSuccess(t *testing.T, actual error) (ok bool) {
+ t.Helper()
+ if actual != nil {
+ t.Errorf("expected success, got error %q instead\n", actual.Error())
+ return false
+ }
+ return true
+}
+
+func expectHeaders(t *testing.T, actual map[string]string, expected map[string]string) {
+ t.Helper()
+ reported := make(map[string]bool)
+
+ for k, av := range actual {
+ ev, exists := expected[k]
+ if !exists {
+ ev = "<not set>"
+ }
+ if av != ev {
+ t.Errorf(`expected "%s: %s", got "%s: %s" instead`, k, ev, k, av)
+ reported[k] = true
+ }
+ }
+
+ for k, ev := range expected {
+ av, exists := actual[k]
+ if !exists {
+ av = "<not set>"
+ }
+ if av != ev && !reported[k] {
+ t.Errorf(`expected "%s: %s", got "%s: %s" instead`, k, ev, k, av)
+ }
+ }
+}