Compare commits
10 Commits
8d85d645d6
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 125ce9c955 | |||
| 131f19deed | |||
| 2ac552e840 | |||
| 48c50a5b7e | |||
| 48f770f703 | |||
| 2ae14cdfd4 | |||
| a10593a318 | |||
| 0971301562 | |||
| 47befe6db1 | |||
| 979ebee677 |
1
go.mod
1
go.mod
@@ -14,5 +14,6 @@ require (
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@@ -67,6 +67,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU=
|
||||
@@ -94,3 +96,5 @@ gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,94 +1,9 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"time"
|
||||
import "context"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
//go:embed setup.sql
|
||||
var setupSql string
|
||||
|
||||
const DB_NAME = "s3Browser"
|
||||
|
||||
type DB struct {
|
||||
dbConn *sql.DB
|
||||
}
|
||||
|
||||
func NewDB(dataSourceName string) (*DB, error) {
|
||||
db, err := sql.Open("mysql", dataSourceName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db.SetConnMaxLifetime(time.Minute * 3)
|
||||
db.SetMaxOpenConns(10)
|
||||
db.SetMaxIdleConns(10)
|
||||
|
||||
return &DB{
|
||||
dbConn: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DB) Setup() error {
|
||||
tx, err := d.dbConn.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(setupSql)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) CheckLogin(ctx context.Context, username, password string) (bool, error) {
|
||||
rows, err := d.dbConn.QueryContext(ctx, "SELECT password FROM user WHERE username = ?", username)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !rows.Next() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var passwordHash []byte
|
||||
err = rows.Scan(&passwordHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if bcrypt.CompareHashAndPassword(passwordHash, []byte(password)) != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (d *DB) AddUser(ctx context.Context, username, password string) error {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = d.dbConn.ExecContext(ctx, "INSERT INTO user (username,password) VALUES (?,?)", username, hash)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
type DB interface {
|
||||
Setup() error
|
||||
CheckLogin(ctx context.Context, username, password string) (bool, error)
|
||||
AddUser(ctx context.Context, username, password string) error
|
||||
}
|
||||
|
||||
94
internal/db/mysql.go
Normal file
94
internal/db/mysql.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
//go:embed setup.sql
|
||||
var setupSql string
|
||||
|
||||
const DB_NAME = "s3Browser"
|
||||
|
||||
type mysqlDB struct {
|
||||
dbConn *sql.DB
|
||||
}
|
||||
|
||||
func NewDB(dataSourceName string) (DB, error) {
|
||||
db, err := sql.Open("mysql", dataSourceName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db.SetConnMaxLifetime(time.Minute * 3)
|
||||
db.SetMaxOpenConns(10)
|
||||
db.SetMaxIdleConns(10)
|
||||
|
||||
return &mysqlDB{
|
||||
dbConn: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *mysqlDB) Setup() error {
|
||||
tx, err := d.dbConn.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(setupSql)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *mysqlDB) CheckLogin(ctx context.Context, username, password string) (bool, error) {
|
||||
rows, err := d.dbConn.QueryContext(ctx, "SELECT password FROM user WHERE username = ?", username)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !rows.Next() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var passwordHash []byte
|
||||
err = rows.Scan(&passwordHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if bcrypt.CompareHashAndPassword(passwordHash, []byte(password)) != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (d *mysqlDB) AddUser(ctx context.Context, username, password string) error {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = d.dbConn.ExecContext(ctx, "INSERT INTO user (username,password) VALUES (?,?)", username, hash)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
75
internal/gql/gql_test.go
Normal file
75
internal/gql/gql_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package gql_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.kapelle.org/niklas/s3browser/internal/gql"
|
||||
"git.kapelle.org/niklas/s3browser/internal/loader"
|
||||
"git.kapelle.org/niklas/s3browser/internal/s3"
|
||||
"github.com/graph-gophers/dataloader"
|
||||
"github.com/graphql-go/graphql"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func setup(t *testing.T) (*assert.Assertions, context.Context, graphql.Schema) {
|
||||
assert := assert.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
schema, _ := gql.GraphqlSchema()
|
||||
|
||||
s3, err := s3.NewMockS3([]string{"bucket1"})
|
||||
assert.NoError(err)
|
||||
ctx = context.WithValue(ctx, "s3Client", s3)
|
||||
|
||||
loader := loader.NewLoader(loader.CacheConfig{
|
||||
ListObjectsLoaderCache: &dataloader.NoCache{},
|
||||
ListObjectsRecursiveLoaderCache: &dataloader.NoCache{},
|
||||
StatObjectLoaderCache: &dataloader.NoCache{},
|
||||
ListBucketsLoaderCache: &dataloader.NoCache{},
|
||||
})
|
||||
assert.NotNil(loader)
|
||||
ctx = context.WithValue(ctx, "loader", loader)
|
||||
|
||||
return assert, ctx, schema
|
||||
}
|
||||
|
||||
func do(ctx context.Context, schema graphql.Schema, query string) *graphql.Result {
|
||||
params := graphql.Params{
|
||||
Schema: schema,
|
||||
RequestString: query,
|
||||
Context: ctx,
|
||||
}
|
||||
r := graphql.Do(params)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestCreateSchema(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.NotPanics(func() {
|
||||
gql.GraphqlTypes()
|
||||
})
|
||||
|
||||
var schema graphql.Schema
|
||||
var err error
|
||||
|
||||
assert.NotPanics(func() {
|
||||
schema, err = gql.GraphqlSchema()
|
||||
})
|
||||
|
||||
assert.NoError(err)
|
||||
assert.NotNil(schema)
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
assert, ctx, schema := setup(t)
|
||||
|
||||
r := do(ctx, schema, `
|
||||
{
|
||||
authorized
|
||||
}
|
||||
`)
|
||||
t.Logf("Data: %v", r.Data)
|
||||
assert.Len(r.Errors, 0)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
types "git.kapelle.org/niklas/s3browser/internal/types"
|
||||
)
|
||||
|
||||
var typesInit bool = false
|
||||
var graphqlDirType *graphql.Object
|
||||
var graphqlFileType *graphql.Object
|
||||
var graphqlLoginResultType *graphql.Object
|
||||
@@ -246,6 +247,8 @@ func GraphqlTypes() {
|
||||
},
|
||||
})
|
||||
|
||||
typesInit = true
|
||||
|
||||
}
|
||||
|
||||
//loadFile helper func for using the dataloader to get a file
|
||||
|
||||
@@ -222,7 +222,7 @@ func deleteDirectory(ctx context.Context, id types.ID) error {
|
||||
//login Checks for valid username password combination. Returns singed jwt string
|
||||
func login(ctx context.Context, username, password string) (types.LoginResult, error) {
|
||||
|
||||
dbStore := ctx.Value("dbStore").(*db.DB)
|
||||
dbStore := ctx.Value("dbStore").(db.DB)
|
||||
|
||||
succes, err := dbStore.CheckLogin(ctx, username, password)
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ import (
|
||||
//GraphqlSchema generate the schema with its root query and mutation
|
||||
func GraphqlSchema() (graphql.Schema, error) {
|
||||
|
||||
if !typesInit {
|
||||
GraphqlTypes()
|
||||
}
|
||||
|
||||
queryFields := graphql.Fields{
|
||||
"files": &graphql.Field{
|
||||
Type: graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphqlFileType))),
|
||||
|
||||
@@ -23,11 +23,18 @@ type Loader struct {
|
||||
listBucketsLoaderCache cache.S3Cache
|
||||
}
|
||||
|
||||
func NewLoader(config types.AppConfig) *Loader {
|
||||
listObjectsLoaderCache := &dataloader.NoCache{}
|
||||
listObjectsRecursiveLoaderCache := &dataloader.NoCache{}
|
||||
statObjectLoaderCache := cache.NewTTLCache(config.CacheTTL, config.CacheCleanup)
|
||||
listBucketsLoaderCache := cache.NewTTLCache(config.CacheTTL, config.CacheCleanup)
|
||||
type CacheConfig struct {
|
||||
ListObjectsLoaderCache cache.S3Cache
|
||||
ListObjectsRecursiveLoaderCache cache.S3Cache
|
||||
StatObjectLoaderCache cache.S3Cache
|
||||
ListBucketsLoaderCache cache.S3Cache
|
||||
}
|
||||
|
||||
func NewLoader(cacheConfig CacheConfig) *Loader {
|
||||
listObjectsLoaderCache := cacheConfig.ListObjectsLoaderCache
|
||||
listObjectsRecursiveLoaderCache := cacheConfig.ListObjectsRecursiveLoaderCache
|
||||
statObjectLoaderCache := cacheConfig.StatObjectLoaderCache
|
||||
listBucketsLoaderCache := cacheConfig.ListBucketsLoaderCache
|
||||
|
||||
return &Loader{
|
||||
listObjectsLoader: dataloader.NewBatchedLoader(
|
||||
|
||||
105
internal/loader/loader_test.go
Normal file
105
internal/loader/loader_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package loader_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.kapelle.org/niklas/s3browser/internal/loader"
|
||||
"git.kapelle.org/niklas/s3browser/internal/s3"
|
||||
"git.kapelle.org/niklas/s3browser/internal/types"
|
||||
"github.com/graph-gophers/dataloader"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func setup(t *testing.T) (context.Context, *loader.Loader, *assert.Assertions) {
|
||||
assert := assert.New(t)
|
||||
s3, _ := s3.NewMockS3([]string{"bucket1", "bucket2"})
|
||||
loader := loader.NewLoader(loader.CacheConfig{
|
||||
ListObjectsLoaderCache: &dataloader.NoCache{},
|
||||
ListObjectsRecursiveLoaderCache: &dataloader.NoCache{},
|
||||
StatObjectLoaderCache: &dataloader.NoCache{},
|
||||
ListBucketsLoaderCache: &dataloader.NoCache{},
|
||||
})
|
||||
|
||||
fillS3(s3)
|
||||
ctx := context.WithValue(context.Background(), "s3Client", s3)
|
||||
|
||||
return ctx, loader, assert
|
||||
}
|
||||
|
||||
func fillS3(s3 s3.S3Service) {
|
||||
ctx := context.Background()
|
||||
length := int64(len("content"))
|
||||
|
||||
for _, v := range []string{
|
||||
"bucket1:/file1", "bucket1:/file2", "bucket1:/dir1/file1",
|
||||
"bucket1:/dir1/file2", "bucket1:/dir2/file1", "bucket1:/dir1/sub1/file1",
|
||||
"bucket1:/dir1/sub1/file2",
|
||||
} {
|
||||
s3.PutObject(ctx, *types.ParseID(v), strings.NewReader("content"), length)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateLoader(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
loader := loader.NewLoader(loader.CacheConfig{
|
||||
ListObjectsLoaderCache: &dataloader.NoCache{},
|
||||
ListObjectsRecursiveLoaderCache: &dataloader.NoCache{},
|
||||
StatObjectLoaderCache: &dataloader.NoCache{},
|
||||
ListBucketsLoaderCache: &dataloader.NoCache{},
|
||||
})
|
||||
|
||||
assert.NotNil(loader)
|
||||
}
|
||||
|
||||
func TestGetBuckets(t *testing.T) {
|
||||
ctx, loader, assert := setup(t)
|
||||
|
||||
buckets, err := loader.GetBuckets(ctx)
|
||||
assert.NoError(err)
|
||||
assert.Len(buckets, 2)
|
||||
assert.Contains(buckets, "bucket1")
|
||||
assert.Contains(buckets, "bucket2")
|
||||
}
|
||||
|
||||
func TestGetFile(t *testing.T) {
|
||||
ctx, loader, assert := setup(t)
|
||||
|
||||
file, err := loader.GetFile(ctx, *types.ParseID("bucket1:/dir1/file1"))
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal("bucket1:/dir1/file1", file.ID.String())
|
||||
assert.Equal("file1", file.Name)
|
||||
assert.Equal(int64(len("content")), file.Size)
|
||||
}
|
||||
|
||||
func TestGetFiles(t *testing.T) {
|
||||
ctx, loader, assert := setup(t)
|
||||
|
||||
id := types.ParseID("bucket1:/")
|
||||
|
||||
files, err := loader.GetFiles(ctx, *id)
|
||||
assert.NoError(err)
|
||||
assert.Len(files, 2)
|
||||
}
|
||||
|
||||
func TestGetDir(t *testing.T) {
|
||||
ctx, loader, assert := setup(t)
|
||||
|
||||
id := types.ParseID("bucket1:/")
|
||||
|
||||
dirs, err := loader.GetDirs(ctx, *id)
|
||||
assert.NoError(err)
|
||||
assert.Len(dirs, 2)
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
ctx, loader, assert := setup(t)
|
||||
|
||||
id := types.ParseID("bucket1:/dir1/")
|
||||
|
||||
files, err := loader.GetFilesRecursive(ctx, *id)
|
||||
assert.NoError(err)
|
||||
assert.Len(files, 4)
|
||||
}
|
||||
@@ -47,14 +47,30 @@ func (m *mockS3) ListBuckets(ctx context.Context) ([]string, error) {
|
||||
func (m *mockS3) ListObjects(ctx context.Context, id types.ID) ([]Object, error) {
|
||||
var results []Object
|
||||
|
||||
dirs := make(map[string]bool)
|
||||
|
||||
depth := len(strings.Split(id.Key, "/"))
|
||||
|
||||
for k, v := range m.objects {
|
||||
if k.Bucket == id.Bucket {
|
||||
if k.Parent().Key == id.Key {
|
||||
results = append(results, *mockObjToObject(v, k))
|
||||
} else if strings.HasPrefix(k.Key, id.Key) {
|
||||
s := strings.Join(strings.Split(k.Key, "/")[:depth], "/") + "/"
|
||||
dirs[s] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k := range dirs {
|
||||
results = append(results, Object{
|
||||
ID: types.ID{
|
||||
Bucket: id.Bucket,
|
||||
Key: k,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -64,12 +80,10 @@ func (m *mockS3) ListObjectsRecursive(ctx context.Context, id types.ID) ([]Objec
|
||||
for k, v := range m.objects {
|
||||
if k.Bucket == id.Bucket {
|
||||
if strings.HasPrefix(k.Key, id.Key) {
|
||||
if k.Parent().Key == id.Key {
|
||||
results = append(results, *mockObjToObject(v, k))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
162
internal/s3/mock_test.go
Normal file
162
internal/s3/mock_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package s3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.kapelle.org/niklas/s3browser/internal/s3"
|
||||
"git.kapelle.org/niklas/s3browser/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func setup(t *testing.T) (s3.S3Service, context.Context, *assert.Assertions) {
|
||||
service, _ := s3.NewMockS3([]string{"bucket1", "bucket2"})
|
||||
ctx := context.Background()
|
||||
assert := assert.New(t)
|
||||
|
||||
return service, ctx, assert
|
||||
}
|
||||
|
||||
func TestBuckets(t *testing.T) {
|
||||
s3, ctx, assert := setup(t)
|
||||
|
||||
buckets, err := s3.ListBuckets(ctx)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Len(buckets, 2)
|
||||
|
||||
assert.Contains(buckets, "bucket1")
|
||||
assert.Contains(buckets, "bucket2")
|
||||
}
|
||||
|
||||
func TestPut(t *testing.T) {
|
||||
s3, ctx, assert := setup(t)
|
||||
content := "FileContent"
|
||||
|
||||
err := s3.PutObject(ctx, *types.ParseID("bucket1:/file1"), strings.NewReader(content), int64(len(content)))
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestPutAndGet(t *testing.T) {
|
||||
s3, ctx, assert := setup(t)
|
||||
|
||||
content := "FileContent"
|
||||
id := *types.ParseID("bucket1:/file1")
|
||||
|
||||
err := s3.PutObject(ctx, *types.ParseID("bucket1:/file1"), strings.NewReader(content), int64(len(content)))
|
||||
assert.NoError(err)
|
||||
|
||||
reader, err := s3.GetObject(ctx, id)
|
||||
assert.NoError(err)
|
||||
|
||||
readerContent, err := ioutil.ReadAll(reader)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal(content, string(readerContent))
|
||||
}
|
||||
|
||||
func TestStat(t *testing.T) {
|
||||
s3, ctx, assert := setup(t)
|
||||
|
||||
content := "FileContent"
|
||||
id := *types.ParseID("bucket1:/file1")
|
||||
|
||||
now := time.Now()
|
||||
|
||||
err := s3.PutObject(ctx, id, strings.NewReader(content), int64(len(content)))
|
||||
assert.NoError(err)
|
||||
|
||||
obj, err := s3.StatObject(ctx, id)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal(id.String(), obj.ID.String())
|
||||
assert.Equal(int64(len(content)), obj.Size)
|
||||
assert.NotEmpty(obj.ETag)
|
||||
assert.WithinDuration(now, obj.LastModified, time.Second*1)
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
s3, ctx, assert := setup(t)
|
||||
|
||||
content := "FileContent"
|
||||
id := *types.ParseID("bucket1:/file1")
|
||||
|
||||
err := s3.PutObject(ctx, id, strings.NewReader(content), int64(len(content)))
|
||||
assert.NoError(err)
|
||||
|
||||
err = s3.RemoveObject(ctx, id)
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = s3.StatObject(ctx, id)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
s3, ctx, assert := setup(t)
|
||||
|
||||
content1 := "FileContent1"
|
||||
id1 := *types.ParseID("bucket1:/file1")
|
||||
|
||||
err := s3.PutObject(ctx, id1, strings.NewReader(content1), int64(len(content1)))
|
||||
assert.NoError(err)
|
||||
|
||||
content2 := "FileContent2"
|
||||
id2 := *types.ParseID("bucket1:/file2")
|
||||
|
||||
err = s3.PutObject(ctx, id2, strings.NewReader(content2), int64(len(content2)))
|
||||
assert.NoError(err)
|
||||
|
||||
listID := types.ParseID("bucket1:/")
|
||||
|
||||
objects, err := s3.ListObjects(ctx, *listID)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Len(objects, 2)
|
||||
}
|
||||
|
||||
func TestListRecursive(t *testing.T) {
|
||||
s3, ctx, assert := setup(t)
|
||||
|
||||
s3.PutObject(ctx, *types.ParseID("bucket1:/file1"), strings.NewReader("content"), int64(len("content")))
|
||||
s3.PutObject(ctx, *types.ParseID("bucket1:/path1/file1"), strings.NewReader("content"), int64(len("content")))
|
||||
s3.PutObject(ctx, *types.ParseID("bucket1:/path1/file2"), strings.NewReader("content"), int64(len("content")))
|
||||
s3.PutObject(ctx, *types.ParseID("bucket1:/path1/path2/file1"), strings.NewReader("content"), int64(len("content")))
|
||||
s3.PutObject(ctx, *types.ParseID("bucket1:/path3/path4/file1"), strings.NewReader("content"), int64(len("content")))
|
||||
|
||||
objects, err := s3.ListObjectsRecursive(ctx, *types.ParseID("bucket1:/path1/"))
|
||||
assert.NoError(err)
|
||||
assert.Len(objects, 3)
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
s3, ctx, assert := setup(t)
|
||||
|
||||
id1 := *types.ParseID("bucket1:/file1")
|
||||
id2 := *types.ParseID("bucket1:/file2")
|
||||
|
||||
s3.PutObject(ctx, id1, strings.NewReader("content"), int64(len("content")))
|
||||
|
||||
err := s3.CopyObject(ctx, id1, id2)
|
||||
assert.NoError(err)
|
||||
|
||||
obj1, err := s3.StatObject(ctx, id1)
|
||||
assert.NoError(err)
|
||||
assert.NotNil(obj1)
|
||||
|
||||
obj2, err := s3.StatObject(ctx, id1)
|
||||
assert.NoError(err)
|
||||
assert.NotNil(obj2)
|
||||
|
||||
assert.Equal(obj1.ETag, obj2.ETag)
|
||||
assert.Equal(obj1.Size, obj2.Size)
|
||||
|
||||
obj2Reader, err := s3.GetObject(ctx, id2)
|
||||
assert.NoError(err)
|
||||
|
||||
obj2Content, err := ioutil.ReadAll(obj2Reader)
|
||||
assert.NoError(err)
|
||||
assert.Equal([]byte("content"), obj2Content)
|
||||
}
|
||||
@@ -3,8 +3,10 @@ package s3browser
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/graph-gophers/dataloader"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"git.kapelle.org/niklas/s3browser/internal/cache"
|
||||
"git.kapelle.org/niklas/s3browser/internal/db"
|
||||
gql "git.kapelle.org/niklas/s3browser/internal/gql"
|
||||
httpserver "git.kapelle.org/niklas/s3browser/internal/httpserver"
|
||||
@@ -33,7 +35,12 @@ func Start(config types.AppConfig) {
|
||||
log.Error("Failed to connect DB: ", err.Error())
|
||||
}
|
||||
|
||||
loader := loader.NewLoader(config)
|
||||
loader := loader.NewLoader(loader.CacheConfig{
|
||||
ListObjectsLoaderCache: &dataloader.NoCache{},
|
||||
ListObjectsRecursiveLoaderCache: &dataloader.NoCache{},
|
||||
StatObjectLoaderCache: cache.NewTTLCache(config.CacheTTL, config.CacheCleanup),
|
||||
ListBucketsLoaderCache: cache.NewTTLCache(config.CacheTTL, config.CacheCleanup),
|
||||
})
|
||||
|
||||
gql.GraphqlTypes()
|
||||
schema, err := gql.GraphqlSchema()
|
||||
|
||||
@@ -69,9 +69,15 @@ func (i ID) Parent() *ID {
|
||||
Key: strings.Join(parts[:len(parts)-2], "/") + "/",
|
||||
}
|
||||
} else {
|
||||
dir := filepath.Dir(i.Key)
|
||||
|
||||
if dir != "/" {
|
||||
dir += "/"
|
||||
}
|
||||
|
||||
parent = &ID{
|
||||
Bucket: i.Bucket,
|
||||
Key: filepath.Dir(i.Key) + "/",
|
||||
Key: dir,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
109
internal/types/id_test.go
Normal file
109
internal/types/id_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.kapelle.org/niklas/s3browser/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TODO: test version component (not yet used in code)
|
||||
|
||||
func TestIDParse(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
id := types.ParseID("test:/path/key")
|
||||
|
||||
assert.NotNil(id)
|
||||
assert.True(id.Valid())
|
||||
assert.Equal("test", id.Bucket)
|
||||
assert.Equal("/path/key", id.Key)
|
||||
assert.False(id.IsDirectory())
|
||||
assert.Equal("key", id.Name())
|
||||
assert.Equal("test:/path/key", id.String())
|
||||
}
|
||||
|
||||
func TestIDParseInvalid(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Nil(types.ParseID("/asd/ad"))
|
||||
assert.Nil(types.ParseID("test"))
|
||||
assert.Nil(types.ParseID("test:"))
|
||||
assert.Nil(types.ParseID(""))
|
||||
assert.Nil(types.ParseID("/"))
|
||||
}
|
||||
|
||||
func TestIDIsDir(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
idFile := types.ParseID("test:/path/key")
|
||||
assert.NotNil(idFile)
|
||||
assert.False(idFile.IsDirectory())
|
||||
|
||||
idDir := types.ParseID("test:/path/key/")
|
||||
assert.NotNil(idDir)
|
||||
assert.True(idDir.IsDirectory())
|
||||
}
|
||||
|
||||
func TestIDRoot(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
id := types.ParseID("test:/")
|
||||
|
||||
assert.NotNil(id)
|
||||
assert.True(id.Valid())
|
||||
assert.Equal("test", id.Bucket)
|
||||
assert.Equal("/", id.Key)
|
||||
assert.True(id.IsDirectory())
|
||||
assert.Equal("/", id.Name())
|
||||
assert.Equal("test:/", id.String())
|
||||
|
||||
assert.Nil(id.Parent())
|
||||
}
|
||||
|
||||
func TestIDParentFromFile(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
id := types.ParseID("test:/path1/path2/key")
|
||||
|
||||
assert.NotNil(id)
|
||||
|
||||
parent := id.Parent()
|
||||
|
||||
assert.NotNil(parent)
|
||||
assert.True(parent.Valid())
|
||||
assert.Equal("test", parent.Bucket)
|
||||
assert.Equal("/path1/path2/", parent.Key)
|
||||
assert.True(parent.IsDirectory())
|
||||
assert.Equal("path2", parent.Name())
|
||||
assert.Equal("test:/path1/path2/", parent.String())
|
||||
}
|
||||
|
||||
func TestIDParentFromDir(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
id := types.ParseID("test:/path1/path2/")
|
||||
|
||||
assert.NotNil(id)
|
||||
|
||||
parent := id.Parent()
|
||||
|
||||
assert.NotNil(parent)
|
||||
assert.True(parent.Valid())
|
||||
assert.Equal("test", parent.Bucket)
|
||||
assert.Equal("/path1/", parent.Key)
|
||||
assert.True(parent.IsDirectory())
|
||||
assert.Equal("path1", parent.Name())
|
||||
assert.Equal("test:/path1/", parent.String())
|
||||
}
|
||||
|
||||
func TestIDParentRoot(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
id := types.ParseID("test:/key1")
|
||||
|
||||
parent := id.Parent()
|
||||
|
||||
assert.NotNil(parent)
|
||||
assert.Equal("/", parent.Key)
|
||||
}
|
||||
Reference in New Issue
Block a user