feat: implementes unit tests for all the bucket action controllers.

This commit is contained in:
niksis02
2025-07-07 21:23:27 +04:00
parent 65cd44aadd
commit 866b07b98f
13 changed files with 2931 additions and 26 deletions

View File

@@ -385,7 +385,7 @@ func CheckIfAccountsExist(accs []string, iam IAMService) ([]string, error) {
for _, acc := range accs {
_, err := iam.GetUserAccount(acc)
if err != nil {
if err == ErrNoSuchUser {
if err == ErrNoSuchUser || err == s3err.GetAPIError(s3err.ErrAdminUserNotFound) {
result = append(result, acc)
continue
}

1
go.mod
View File

@@ -51,6 +51,7 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/net v0.42.0 // indirect

11
go.sum
View File

@@ -118,6 +118,10 @@ github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXw
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
@@ -152,6 +156,8 @@ github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@@ -163,8 +169,9 @@ github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c
github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -259,6 +266,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,150 @@
// Copyright 2023 Versity Software
// This file is 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 controllers
import (
"bytes"
"net/http"
"net/http/httptest"
"net/url"
"path"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/s3api/utils"
"github.com/versity/versitygw/s3err"
)
var (
defaultLocals map[utils.ContextKey]any = map[utils.ContextKey]any{
utils.ContextKeyIsRoot: true,
utils.ContextKeyParsedAcl: auth.ACL{
Owner: "root",
},
utils.ContextKeyAccount: auth.Account{
Access: "root",
Role: auth.RoleAdmin,
},
}
accessDeniedLocals map[utils.ContextKey]any = map[utils.ContextKey]any{
utils.ContextKeyIsRoot: false,
utils.ContextKeyParsedAcl: auth.ACL{
Owner: "root",
},
utils.ContextKeyAccount: auth.Account{
Access: "user",
Role: auth.RoleUser,
},
}
)
type testInput struct {
bucket string
object string
body []byte
locals map[utils.ContextKey]any
headers map[string]string
queries map[string]string
beRes any
beErr error
extraMockErr error
}
type testOutput struct {
response *Response
err error
}
type ctxInputs struct {
bucket string
object string
body []byte
locals map[utils.ContextKey]any
headers map[string]string
queries map[string]string
}
func testController(t *testing.T, ctrl Controller, resp *Response, expectedErr error, input ctxInputs) {
app := fiber.New()
app.Post("/:bucket/*", func(ctx *fiber.Ctx) error {
// set the request body
ctx.Request().SetBody(input.body)
// set the request locals
if input.locals != nil {
for key, local := range input.locals {
key.Set(ctx, local)
}
}
// call the controller by passing the ctx
res, err := ctrl(ctx)
assert.Equal(t, resp, res)
if expectedErr != nil {
assert.Error(t, err)
switch expectedErr.(type) {
case s3err.APIError:
assert.EqualValues(t, expectedErr, err)
default:
assert.ErrorContains(t, err, expectedErr.Error())
}
} else {
assert.NoError(t, err)
}
return nil
})
req := buildRequest(input.bucket, input.object, input.body, input.headers, input.queries)
_, err := app.Test(req)
assert.NoError(t, err)
}
func buildRequest(bucket, object string, body []byte, headers, queries map[string]string) *http.Request {
if bucket == "" {
bucket = "bucket"
}
if object == "" {
object = "object"
}
uri := url.URL{
Path: "/" + path.Join(bucket, object),
}
// set the request query params
if queries != nil {
q := uri.Query()
for key, val := range queries {
q.Set(key, val)
}
uri.RawQuery = q.Encode()
}
// create a new request
req := httptest.NewRequest(http.MethodPost, uri.String(), bytes.NewReader(body))
// set the request headers
for key, val := range headers {
req.Header.Add(key, val)
}
return req
}

View File

@@ -0,0 +1,411 @@
// Copyright 2023 Versity Software
// This file is 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 controllers
import (
"context"
"net/http"
"testing"
"github.com/versity/versitygw/s3err"
)
func TestS3ApiController_DeleteBucketTagging(t *testing.T) {
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "backend returns error",
input: testInput{
locals: defaultLocals,
beErr: s3err.GetAPIError(s3err.ErrAclNotSupported),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
err: s3err.GetAPIError(s3err.ErrAclNotSupported),
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
DeleteBucketTaggingFunc: func(_ context.Context, _ string) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.DeleteBucketTagging,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
})
})
}
}
func TestS3ApiController_DeleteBucketOwnershipControls(t *testing.T) {
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "backend returns error",
input: testInput{
locals: defaultLocals,
beErr: s3err.GetAPIError(s3err.ErrInvalidAccessKeyID),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
err: s3err.GetAPIError(s3err.ErrInvalidAccessKeyID),
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
DeleteBucketOwnershipControlsFunc: func(contextMoqParam context.Context, bucket string) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.DeleteBucketOwnershipControls,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
})
})
}
}
func TestS3ApiController_DeleteBucketPolicy(t *testing.T) {
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "backend returns error",
input: testInput{
locals: defaultLocals,
beErr: s3err.GetAPIError(s3err.ErrInvalidDigest),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
err: s3err.GetAPIError(s3err.ErrInvalidDigest),
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
DeleteBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.DeleteBucketPolicy,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
})
})
}
}
func TestS3ApiController_DeleteBucketCors(t *testing.T) {
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "backend returns error",
input: testInput{
locals: defaultLocals,
beErr: s3err.GetAPIError(s3err.ErrAdminMethodNotSupported),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAdminMethodNotSupported),
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
DeleteBucketCorsFunc: func(contextMoqParam context.Context, bucket string) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.DeleteBucketCors,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
})
})
}
}
func TestS3ApiController_DeleteBucket(t *testing.T) {
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "backend returns error",
input: testInput{
locals: defaultLocals,
beErr: s3err.GetAPIError(s3err.ErrInvalidDigest),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
err: s3err.GetAPIError(s3err.ErrInvalidDigest),
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
DeleteBucketFunc: func(contextMoqParam context.Context, bucket string) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.DeleteBucket,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
})
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,136 @@
// Copyright 2023 Versity Software
// This file is 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 controllers
import (
"context"
"testing"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/s3api/utils"
"github.com/versity/versitygw/s3err"
)
func TestS3ApiController_HeadBucket(t *testing.T) {
region := "us-east-1"
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyIsRoot: false,
utils.ContextKeyParsedAcl: auth.ACL{
Owner: "root",
},
utils.ContextKeyAccount: auth.Account{
Access: "user",
Role: auth.RoleUser,
},
utils.ContextKeyRegion: region,
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "backend returns error",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyIsRoot: true,
utils.ContextKeyParsedAcl: auth.ACL{
Owner: "root",
},
utils.ContextKeyAccount: auth.Account{
Access: "root",
Role: auth.RoleAdmin,
},
utils.ContextKeyRegion: region,
},
beErr: s3err.GetAPIError(s3err.ErrInvalidAccessKeyID),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrInvalidAccessKeyID),
},
},
{
name: "successful response",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyIsRoot: true,
utils.ContextKeyParsedAcl: auth.ACL{
Owner: "root",
},
utils.ContextKeyAccount: auth.Account{
Access: "root",
Role: auth.RoleAdmin,
},
utils.ContextKeyRegion: region,
},
},
output: testOutput{
response: &Response{
Headers: map[string]*string{
"X-Amz-Access-Point-Alias": utils.GetStringPtr("false"),
"X-Amz-Bucket-Region": utils.GetStringPtr(region),
},
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
HeadBucketFunc: func(contextMoqParam context.Context, headBucketInput *s3.HeadBucketInput) (*s3.HeadBucketOutput, error) {
return &s3.HeadBucketOutput{}, tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.HeadBucket,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
})
})
}
}

View File

@@ -29,7 +29,6 @@ func (c S3ApiController) ListBuckets(ctx *fiber.Ctx) (*Response, error) {
cToken := ctx.Query("continuation-token")
prefix := ctx.Query("prefix")
maxBucketsStr := ctx.Query("max-buckets")
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
var maxBuckets int32 = 10000

View File

@@ -0,0 +1,112 @@
// Copyright 2023 Versity Software
// This file is 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 controllers
import (
"context"
"testing"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3response"
)
func TestS3ApiController_ListBuckets(t *testing.T) {
validRes := s3response.ListAllMyBucketsResult{
Owner: s3response.CanonicalUser{
ID: "root",
},
Buckets: s3response.ListAllMyBucketsList{
Bucket: []s3response.ListAllMyBucketsEntry{
{Name: "test"},
},
},
}
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "invalid max buckets",
input: testInput{
locals: defaultLocals,
queries: map[string]string{
"max-buckets": "-1",
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{},
},
err: s3err.GetAPIError(s3err.ErrInvalidMaxBuckets),
},
},
{
name: "backend returns error",
input: testInput{
locals: defaultLocals,
beRes: validRes,
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
output: testOutput{
response: &Response{
Data: validRes,
MetaOpts: &MetaOptions{},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
beRes: validRes,
queries: map[string]string{
"max-buckets": "3",
},
},
output: testOutput{
response: &Response{
Data: validRes,
MetaOpts: &MetaOptions{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
ListBucketsFunc: func(contextMoqParam context.Context, listBucketsInput s3response.ListBucketsInput) (s3response.ListAllMyBucketsResult, error) {
return tt.input.beRes.(s3response.ListAllMyBucketsResult), tt.input.beErr
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.ListBuckets,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
queries: tt.input.queries,
})
})
}
}

View File

@@ -37,19 +37,7 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) (*Response, error) {
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
var dObj s3response.DeleteObjects
err := xml.Unmarshal(ctx.Body(), &dObj)
if err != nil {
debuglogger.Logf("error unmarshalling delete objects: %v", err)
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
}
err = auth.VerifyAccess(ctx.Context(), c.be,
err := auth.VerifyAccess(ctx.Context(), c.be,
auth.AccessOptions{
Readonly: c.readonly,
Acl: parsedAcl,
@@ -68,6 +56,17 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) (*Response, error) {
}, err
}
var dObj s3response.DeleteObjects
err = xml.Unmarshal(ctx.Body(), &dObj)
if err != nil {
debuglogger.Logf("error unmarshalling delete objects: %v", err)
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, s3err.GetAPIError(s3err.ErrInvalidRequest)
}
err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, dObj.Objects, bypass, IsBucketPublic, c.be)
if err != nil {
return &Response{

View File

@@ -0,0 +1,165 @@
// Copyright 2023 Versity Software
// This file is 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 controllers
import (
"context"
"encoding/xml"
"testing"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/stretchr/testify/assert"
"github.com/versity/versitygw/s3api/utils"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3event"
"github.com/versity/versitygw/s3response"
)
func TestS3ApiController_DeleteObjects(t *testing.T) {
validBody, err := xml.Marshal(s3response.DeleteObjects{
Objects: []types.ObjectIdentifier{
{Key: utils.GetStringPtr("obj")},
},
})
assert.NoError(t, err)
validRes := s3response.DeleteResult{
Deleted: []types.DeletedObject{
{Key: utils.GetStringPtr("key")},
},
}
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "invalid request body",
input: testInput{
locals: defaultLocals,
body: []byte("invalid_body"),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrInvalidRequest),
},
},
{
name: "check object access returns error",
input: testInput{
locals: defaultLocals,
body: validBody,
extraMockErr: s3err.GetAPIError(s3err.ErrObjectLocked),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrObjectLocked),
},
},
{
name: "backend returns error",
input: testInput{
locals: defaultLocals,
beRes: s3response.DeleteResult{},
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
body: validBody,
extraMockErr: s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound),
},
output: testOutput{
response: &Response{
Data: s3response.DeleteResult{},
MetaOpts: &MetaOptions{
BucketOwner: "root",
EventName: s3event.EventObjectRemovedDeleteObjects,
ObjectCount: 1,
},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
body: validBody,
beRes: validRes,
extraMockErr: s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound),
},
output: testOutput{
response: &Response{
Data: validRes,
MetaOpts: &MetaOptions{
BucketOwner: "root",
EventName: s3event.EventObjectRemovedDeleteObjects,
ObjectCount: 1,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
DeleteObjectsFunc: func(contextMoqParam context.Context, deleteObjectsInput *s3.DeleteObjectsInput) (s3response.DeleteResult, error) {
return tt.input.beRes.(s3response.DeleteResult), tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, tt.input.extraMockErr
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.DeleteObjects,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
})
})
}
}

View File

@@ -0,0 +1,813 @@
// Copyright 2023 Versity Software
// This file is 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 controllers
import (
"context"
"encoding/xml"
"fmt"
"net/http"
"testing"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/stretchr/testify/assert"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/s3api/utils"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3response"
)
func TestS3ApiController_PutBucketTagging(t *testing.T) {
validTaggingBody, err := xml.Marshal(s3response.TaggingInput{
TagSet: s3response.TagSet{
Tags: []s3response.Tag{
{
Key: "key",
Value: "val",
},
},
},
})
assert.NoError(t, err)
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "invalid request body",
input: testInput{
locals: defaultLocals,
body: []byte("invalid_body"),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrMalformedXML),
},
},
{
name: "backend returns error",
input: testInput{
locals: defaultLocals,
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
body: validTaggingBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "successful response",
input: testInput{
locals: defaultLocals,
body: validTaggingBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
Status: http.StatusNoContent,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
PutBucketTaggingFunc: func(contextMoqParam context.Context, bucket string, tags map[string]string) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(
t,
ctrl.PutBucketTagging,
tt.output.response,
tt.output.err,
ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
})
})
}
}
func TestS3ApiController_PutBucketOwnershipControls(t *testing.T) {
validOwnershipBody, err := xml.Marshal(
s3response.OwnershipControls{
Rules: []types.OwnershipControlsRule{
{ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced},
},
})
assert.NoError(t, err)
invalidRuleCountBody, err := xml.Marshal(
s3response.OwnershipControls{
Rules: []types.OwnershipControlsRule{
{ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced},
{ObjectOwnership: types.ObjectOwnershipBucketOwnerPreferred},
},
},
)
assert.NoError(t, err)
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "invalid request body",
input: testInput{
locals: defaultLocals,
body: []byte("invalid_body"),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrMalformedXML),
},
},
{
name: "invalid rules count",
input: testInput{
locals: defaultLocals,
body: invalidRuleCountBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrMalformedXML),
},
},
{
name: "backend error",
input: testInput{
locals: defaultLocals,
body: validOwnershipBody,
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "success",
input: testInput{
locals: defaultLocals,
body: validOwnershipBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
PutBucketOwnershipControlsFunc: func(contextMoqParam context.Context, bucket string, ownership types.ObjectOwnership) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(t, ctrl.PutBucketOwnershipControls, tt.output.response, tt.output.err, ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
})
})
}
}
func TestS3ApiController_PutBucketVersioning(t *testing.T) {
validVersioningBody, err := xml.Marshal(
types.VersioningConfiguration{
Status: types.BucketVersioningStatusEnabled,
},
)
assert.NoError(t, err)
invalidVersioningStatusBody, err := xml.Marshal(
types.VersioningConfiguration{
Status: types.BucketVersioningStatus("invalid_status"),
},
)
assert.NoError(t, err)
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "invalid request body",
input: testInput{
locals: defaultLocals,
body: []byte("invalid_body"),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrInvalidRequest),
},
},
{
name: "invalid rules count",
input: testInput{
locals: defaultLocals,
body: invalidVersioningStatusBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrMalformedXML),
},
},
{
name: "backend error",
input: testInput{
locals: defaultLocals,
body: validVersioningBody,
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "success",
input: testInput{
locals: defaultLocals,
body: validVersioningBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
PutBucketVersioningFunc: func(contextMoqParam context.Context, bucket string, status types.BucketVersioningStatus) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(t, ctrl.PutBucketVersioning, tt.output.response, tt.output.err, ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
})
})
}
}
func TestS3ApiController_PutObjectLockConfiguration(t *testing.T) {
validLockBody, err := xml.Marshal(
types.ObjectLockConfiguration{
ObjectLockEnabled: types.ObjectLockEnabledEnabled,
},
)
assert.NoError(t, err)
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "invalid request body",
input: testInput{
locals: defaultLocals,
body: []byte("invalid_body"),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrMalformedXML),
},
},
{
name: "backend error",
input: testInput{
locals: defaultLocals,
body: validLockBody,
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "success",
input: testInput{
locals: defaultLocals,
body: validLockBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string, config []byte) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(t, ctrl.PutObjectLockConfiguration, tt.output.response, tt.output.err, ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
})
})
}
}
func TestS3ApiController_PutBucketCors(t *testing.T) {
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "backend error",
input: testInput{
locals: defaultLocals,
beErr: s3err.GetAPIError(s3err.ErrNotImplemented),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrNotImplemented),
},
},
{
name: "success",
input: testInput{
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
PutBucketCorsFunc: func(contextMoqParam context.Context, bytes []byte) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(t, ctrl.PutBucketCors, tt.output.response, tt.output.err, ctxInputs{
locals: tt.input.locals,
})
})
}
}
func TestS3ApiController_PutBucketPolicy(t *testing.T) {
validPolicyDocument :=
`{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bucket/*"
}
]
}`
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "verify access fails",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "invalid policy document",
input: testInput{
locals: defaultLocals,
body: []byte("invalid_body"),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.APIError{
Code: "MalformedPolicy",
Description: "Policies must be valid JSON and the first byte must be '{'",
HTTPStatusCode: http.StatusBadRequest,
},
},
},
{
name: "backend error",
input: testInput{
locals: defaultLocals,
body: []byte(validPolicyDocument),
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: "root"},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "success",
input: testInput{
locals: defaultLocals,
body: []byte(validPolicyDocument),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
PutBucketPolicyFunc: func(contextMoqParam context.Context, bucket string, policy []byte) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
}
testController(t, ctrl.PutBucketPolicy, tt.output.response, tt.output.err, ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
})
})
}
}
func TestS3ApiController_CreateBucket(t *testing.T) {
adminAcc := auth.Account{
Access: "root",
Role: auth.RoleAdmin,
}
userAcc := auth.Account{
Access: "user",
Role: auth.RoleUser,
}
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "access denied",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyAccount: userAcc,
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "invalid bucket name",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyAccount: adminAcc,
},
bucket: "invalid_bucket_name",
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{},
},
err: s3err.GetAPIError(s3err.ErrInvalidBucketName),
},
},
{
name: "invalid ownership",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyAccount: adminAcc,
},
headers: map[string]string{
"X-Amz-Object-Ownership": "invalid_ownership",
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{},
},
err: s3err.APIError{
Code: "InvalidArgument",
Description: "Invalid x-amz-object-ownership header: invalid_ownership",
HTTPStatusCode: http.StatusBadRequest,
},
},
},
{
name: "invalid ownership + acl",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyAccount: adminAcc,
},
headers: map[string]string{
"X-Amz-Object-Ownership": string(types.ObjectOwnershipBucketOwnerEnforced),
"X-Amz-Acl": string(types.BucketCannedACLPublicRead),
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: adminAcc.Access},
},
err: s3err.GetAPIError(s3err.ErrInvalidBucketAclWithObjectOwnership),
},
},
{
name: "both grants and canned acl",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyAccount: adminAcc,
},
headers: map[string]string{
"X-Amz-Acl": string(types.BucketCannedACLPublicRead),
"X-Amz-Grant-Read": userAcc.Access,
"X-Amz-Object-Ownership": string(types.ObjectOwnershipBucketOwnerPreferred),
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: adminAcc.Access},
},
err: s3err.GetAPIError(s3err.ErrBothCannedAndHeaderGrants),
},
},
{
name: "fail to update the acl",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyAccount: adminAcc,
},
headers: map[string]string{
"X-Amz-Grant-Read": userAcc.Access,
"X-Amz-Object-Ownership": string(types.ObjectOwnershipBucketOwnerPreferred),
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: adminAcc.Access},
},
err: fmt.Errorf("accounts does not exist: %s", userAcc.Access),
},
},
{
name: "backend error",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyAccount: adminAcc,
},
beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{BucketOwner: adminAcc.Access},
},
err: s3err.GetAPIError(s3err.ErrNoSuchBucket),
},
},
{
name: "success",
input: testInput{
locals: map[utils.ContextKey]any{
utils.ContextKeyAccount: adminAcc,
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: adminAcc.Access,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
CreateBucketFunc: func(contextMoqParam context.Context, createBucketInput *s3.CreateBucketInput, defaultACL []byte) error {
return tt.input.beErr
},
}
ctrl := S3ApiController{
be: be,
iam: auth.NewIAMServiceSingle(adminAcc),
}
testController(t, ctrl.CreateBucket, tt.output.response, tt.output.err,
ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
bucket: tt.input.bucket,
headers: tt.input.headers,
})
})
}
}
// TODO: add a test for PutBucketAcl

View File

@@ -1297,11 +1297,6 @@ func PresignedAuth_UploadPart(s *S3Conf) error {
return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error {
key, partNumber := "my-mp", int32(1)
err := setup(s, bucket)
if err != nil {
return err
}
clt := s.GetClient()
mp, err := createMp(clt, bucket, key)
if err != nil {
@@ -1351,11 +1346,6 @@ func PresignedAuth_UploadPart(s *S3Conf) error {
return fmt.Errorf("expected uploaded part part-number to be %v, instead got %v", partNumber, *out.Parts[0].PartNumber)
}
err = teardown(s, bucket)
if err != nil {
return err
}
return nil
})
}