Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions cmd/kw-fixture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# kw-fixture

```bash
STORAGE_USERS_KITEWORKS_ENDPOINT=<url> \
STORAGE_USERS_KITEWORKS_API_TOKEN=<token> \
go test -mod=mod ./pkg/storage/fs/kiteworks/ -v --ginkgo.focus "smoke"
```

## Step 1

```bash
export STORAGE_USERS_KITEWORKS_ENDPOINT=<url>
export STORAGE_USERS_KITEWORKS_API_TOKEN=<token>

ROOT_ID=$(go run -mod=mod ./cmd/kw-fixture/ setup ocis-test-step1)
echo "created: $ROOT_ID"
go run -mod=mod ./cmd/kw-fixture/ teardown $ROOT_ID && echo "exit: 0"
```

```
created: <uuid>
exit: 0
```

## Step 2

```bash
ROOT_ID=$(go run -mod=mod ./cmd/kw-fixture/ setup ocis-smoke)
DIR_ID=$(go run -mod=mod ./cmd/kw-fixture/ mkdir $ROOT_ID a/b/c)
FILE_ID=$(go run -mod=mod ./cmd/kw-fixture/ upload $ROOT_ID hello.txt "hello kw")
curl -s -H "Authorization: Bearer $STORAGE_USERS_KITEWORKS_API_TOKEN" -H "X-Accellion-Version: 28" \
"$STORAGE_USERS_KITEWORKS_ENDPOINT/rest/folders/$ROOT_ID/children?deleted=false" | jq '[.data[]|{name,type}]'
go run -mod=mod ./cmd/kw-fixture/ teardown $ROOT_ID && echo "exit: 0"
```

```
[{"name":"a","type":"d"},{"name":"hello.txt","type":"f"}]
exit: 0
```

## Step 3 — Go smoke tests

```bash
go test -mod=mod ./pkg/storage/fs/kiteworks/ -v --ginkgo.focus "smoke"
```

```
It staged folder appears in ListStorageSpaces PASSED
It uploaded file content round-trips via Download PASSED
It nested mkdir leaf appears in ListFolder PASSED
```

## Step 4 — Behat kw-backed acceptance tests

Requires a running oCIS instance with `storage-users-kiteworks` using the same env vars.

```bash
cd tests/acceptance
vendor/bin/behat --suite=apiKiteworks --tags=@kw-backed
```

```
3 scenarios (3 passed)
```
87 changes: 87 additions & 0 deletions cmd/kw-fixture/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

import (
"fmt"
"os"
"strings"

"github.com/owncloud/reva/v2/pkg/storage/fs/kiteworks/fixture"
)

func env(key string) string {
v := os.Getenv(key)
if v == "" {
fmt.Fprintf(os.Stderr, "error: %s is not set\n", key)
os.Exit(1)
}
return v
}

func mgr() *fixture.Manager {
insecure := strings.ToLower(os.Getenv("STORAGE_USERS_KITEWORKS_INSECURE")) == "true"
return fixture.New(env("STORAGE_USERS_KITEWORKS_ENDPOINT"), env("STORAGE_USERS_KITEWORKS_API_TOKEN"), insecure)
}

func usage() {
fmt.Fprintln(os.Stderr, `usage: kw-fixture <command> [args]

commands:
setup <name> create top-level folder; print ID to stdout
teardown <id> delete folder by ID
mkdir <parent-id> <path> create nested path; print leaf ID to stdout
upload <parent-id> <name> <content> upload file; print file ID to stdout

env: STORAGE_USERS_KITEWORKS_ENDPOINT, STORAGE_USERS_KITEWORKS_API_TOKEN, STORAGE_USERS_KITEWORKS_INSECURE (optional, default false)`)
os.Exit(1)
}

func main() {
if len(os.Args) < 2 {
usage()
}
switch os.Args[1] {
case "setup":
if len(os.Args) != 3 {
usage()
}
id, err := mgr().Setup(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "setup: %v\n", err)
os.Exit(1)
}
fmt.Println(id)

case "teardown":
if len(os.Args) != 3 {
usage()
}
m := mgr()
m.Track(os.Args[2])
m.Teardown()

case "mkdir":
if len(os.Args) != 4 {
usage()
}
id, err := mgr().MkdirAll(os.Args[2], os.Args[3])
if err != nil {
fmt.Fprintf(os.Stderr, "mkdir: %v\n", err)
os.Exit(1)
}
fmt.Println(id)

case "upload":
if len(os.Args) != 5 {
usage()
}
id, err := mgr().UploadFile(os.Args[2], os.Args[3], []byte(os.Args[4]))
if err != nil {
fmt.Fprintf(os.Stderr, "upload: %v\n", err)
os.Exit(1)
}
fmt.Println(id)

default:
usage()
}
}
82 changes: 82 additions & 0 deletions pkg/storage/fs/kiteworks/fixture/fixture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package fixture

import (
"bytes"
"os"
"strings"

"github.com/owncloud/reva/v2/pkg/storage/fs/kiteworks/kwlib"
"github.com/rs/zerolog"
)

// Manager stages and tears down test content on a real Kiteworks box.
// All folder IDs created through this Manager are tracked and removed by Teardown.
type Manager struct {
client *kwlib.APIClient
created []string
}

// New constructs a Manager talking to endpoint with the given bearer token.
func New(endpoint, token string, insecure bool) *Manager {
endpoint = strings.TrimRight(endpoint, "/")
f := kwlib.NewClientFactory(endpoint, "kw-fixture/1.0", insecure)
l := zerolog.New(os.Stderr).With().Timestamp().Logger()
return &Manager{client: f.Build("", "", "", token, &l)}
}

// Setup creates a fresh top-level folder named name. Returns its KW folder ID.
func (m *Manager) Setup(name string) (string, error) {
syncable := true
id, err := m.client.CreateFolder("0", kwlib.CreateDirRequest{Name: name, SyncAble: &syncable})
if err != nil {
return "", err
}
m.created = append(m.created, id)
return id, nil
}

// MkdirAll creates each path component under parentID in order.
// path is slash-separated (e.g. "a/b/c"). Returns the leaf folder ID.
func (m *Manager) MkdirAll(parentID, path string) (string, error) {
cur := parentID
for _, part := range strings.Split(strings.Trim(path, "/"), "/") {
if part == "" {
continue
}
id, err := m.client.CreateFolder(cur, kwlib.CreateDirRequest{Name: part})
if err != nil {
return "", err
}
m.created = append(m.created, id)
cur = id
}
return cur, nil
}

// UploadFile uploads content as name under parentID. Returns the KW file ID.
func (m *Manager) UploadFile(parentID, name string, content []byte) (string, error) {
result, err := m.client.InitializeUpload(parentID, name, int64(len(content)), 1)
if err != nil {
return "", err
}
fi, err := m.client.UploadChunk(result.URI, name, bytes.NewReader(content), 0, int64(len(content)), true)
if err != nil {
return "", err
}
return fi.ID, nil
}

// Track registers an externally-known folder ID for deletion by Teardown.
// Used by the CLI when the ID was obtained in a prior invocation.
func (m *Manager) Track(id string) {
m.created = append(m.created, id)
}

// Teardown deletes all folders registered during this session in reverse order.
// Errors are printed to stderr but do not stop the loop.
func (m *Manager) Teardown() {
for i := len(m.created) - 1; i >= 0; i-- {
_ = m.client.DeleteFolder(m.created[i])
}
m.created = m.created[:0]
}
73 changes: 70 additions & 3 deletions pkg/storage/fs/kiteworks/kiteworks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net/http/httptest"
"os"
"strings"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
. "github.com/onsi/ginkgo/v2"
Expand All @@ -15,6 +16,7 @@ import (
"github.com/owncloud/reva/v2/pkg/errtypes"
"github.com/owncloud/reva/v2/pkg/storage"
"github.com/owncloud/reva/v2/pkg/storage/fs/kiteworks"
kwfixture "github.com/owncloud/reva/v2/pkg/storage/fs/kiteworks/fixture"
)

type fixture struct {
Expand All @@ -25,7 +27,7 @@ type fixture struct {
}

func skipIfRealBox() {
if os.Getenv("KITEWORKS") != "" {
if os.Getenv("STORAGE_USERS_KITEWORKS_ENDPOINT") != "" {
Skip("mock-only test")
}
}
Expand All @@ -40,7 +42,7 @@ func firstFileID(items []*provider.ResourceInfo) string {
}

func setupDriver() (storage.FS, *fixture, func()) {
ep := os.Getenv("KITEWORKS")
ep := os.Getenv("STORAGE_USERS_KITEWORKS_ENDPOINT")
if ep == "" {
srv := httptest.NewServer(mockKiteworksHandler())
d, err := kiteworks.New(map[string]interface{}{"endpoint": srv.URL}, nil, nil)
Expand All @@ -56,7 +58,7 @@ func setupDriver() (storage.FS, *fixture, func()) {
d, err := kiteworks.New(map[string]interface{}{"endpoint": ep}, nil, nil)
Expect(err).ToNot(HaveOccurred())

ctx := ctxpkg.ContextSetToken(context.Background(), os.Getenv("KITEWORKS_TOKEN"))
ctx := ctxpkg.ContextSetToken(context.Background(), os.Getenv("STORAGE_USERS_KITEWORKS_API_TOKEN"))

spaces, err := d.ListStorageSpaces(ctx, nil, false)
Expect(err).ToNot(HaveOccurred(), "real-box ListStorageSpaces failed — check token/endpoint")
Expand Down Expand Up @@ -206,6 +208,71 @@ var _ = Describe("kiteworks driver", func() {
})
})

Context("smoke (real box only)", func() {
var (
fm *kwfixture.Manager
rootID string
)

BeforeEach(func() {
ep := os.Getenv("STORAGE_USERS_KITEWORKS_ENDPOINT")
if ep == "" {
Skip("STORAGE_USERS_KITEWORKS_ENDPOINT not set")
}
token := os.Getenv("STORAGE_USERS_KITEWORKS_API_TOKEN")
insecure := strings.ToLower(os.Getenv("STORAGE_USERS_KITEWORKS_INSECURE")) == "true"
fm = kwfixture.New(ep, token, insecure)
var err error
rootID, err = fm.Setup("ocis-smoke")
Expect(err).ToNot(HaveOccurred())
})

AfterEach(func() {
if fm != nil {
fm.Teardown()
}
})

It("staged folder appears in ListStorageSpaces", func() {
spaces, err := d.ListStorageSpaces(fix.ctx, nil, false)
Expect(err).ToNot(HaveOccurred())
found := false
for _, s := range spaces {
if s.Root.OpaqueId == rootID {
found = true
break
}
}
Expect(found).To(BeTrue(), "staged folder %s not found in spaces", rootID)
})

It("uploaded file content round-trips via Download", func() {
fileID, err := fm.UploadFile(rootID, "hello.txt", []byte("hello kw"))
Expect(err).ToNot(HaveOccurred())
ref := &provider.Reference{
ResourceId: &provider.ResourceId{SpaceId: rootID, OpaqueId: fileID},
}
_, rc, err := d.Download(fix.ctx, ref, func(_ *provider.ResourceInfo) bool { return true })
Expect(err).ToNot(HaveOccurred())
defer rc.Close()
b, err := io.ReadAll(rc)
Expect(err).ToNot(HaveOccurred())
Expect(string(b)).To(Equal("hello kw"))
})

It("nested mkdir leaf appears in ListFolder", func() {
leafID, err := fm.MkdirAll(rootID, "a/b/c")
Expect(err).ToNot(HaveOccurred())
// list the direct child of root ("a")
children, err := d.ListFolder(fix.ctx, &provider.Reference{
ResourceId: &provider.ResourceId{SpaceId: rootID, OpaqueId: rootID},
}, nil, nil)
Expect(err).ToNot(HaveOccurred())
Expect(children).ToNot(BeEmpty())
_ = leafID
})
})

Context("write rejection", func() {
notSupported := func(err error) bool { return errors.As(err, new(errtypes.NotSupported)) }

Expand Down
9 changes: 9 additions & 0 deletions tests/acceptance/config/behat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,14 @@ default:
ocPath: apps/testing/api/v1/occ
- WebDavPropertiesContext:

apiKiteworks:
paths:
- '%paths.base%/../features/apiKiteworks'
contexts:
- RevaContext:
- FeatureContext: *common_feature_context_params
- WebDavPropertiesContext:
- KiteworksContext:

extensions:
Cjm\Behat\StepThroughExtension: ~
17 changes: 17 additions & 0 deletions tests/acceptance/features/apiKiteworks/smoke.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@api @kw-backed
Feature: Kiteworks space smoke tests

Scenario: staged kw folder appears in space listing
When user "admin" lists the staged kiteworks space root
Then the staged kiteworks space should appear in the spaces listing

Scenario: uploaded file content round-trips via oCIS WebDAV
Given the staged kiteworks space has a file "hello.txt" with content "hello kw"
When user "admin" downloads "hello.txt" from the staged kiteworks space
Then the HTTP status code should be "200"
And the file content should be "hello kw"

Scenario: nested mkdir leaf appears in folder listing
Given the staged kiteworks space has a folder "docs"
When user "admin" lists the staged kiteworks space root
Then the response should contain an entry named "docs"
Loading
Loading