feat: implements unit tests for controller utilities

This commit is contained in:
niksis02
2025-07-16 00:14:29 +04:00
parent ab571a6571
commit 394675a5a8
10 changed files with 949 additions and 1911 deletions

View File

@@ -41,8 +41,14 @@ type Tag struct {
Value string
}
// Manager is a manager of metrics plugins
type Manager struct {
// Manager is the interface definition for metrics manager
type Manager interface {
Send(ctx *fiber.Ctx, err error, action string, count int64, status int)
Close()
}
// manager is a manager of metrics plugins
type manager struct {
wg sync.WaitGroup
ctx context.Context
@@ -59,7 +65,7 @@ type Config struct {
}
// NewManager initializes metrics plugins and returns a new metrics manager
func NewManager(ctx context.Context, conf Config) (*Manager, error) {
func NewManager(ctx context.Context, conf Config) (Manager, error) {
if len(conf.StatsdServers) == 0 && len(conf.DogStatsdServers) == 0 {
return nil, nil
}
@@ -74,7 +80,7 @@ func NewManager(ctx context.Context, conf Config) (*Manager, error) {
addDataChan := make(chan datapoint, dataItemCount)
mgr := &Manager{
mgr := &manager{
addDataChan: addDataChan,
ctx: ctx,
config: conf,
@@ -112,7 +118,7 @@ func NewManager(ctx context.Context, conf Config) (*Manager, error) {
return mgr, nil
}
func (m *Manager) Send(ctx *fiber.Ctx, err error, action string, count int64, status int) {
func (m *manager) Send(ctx *fiber.Ctx, err error, action string, count int64, status int) {
// In case of Authentication failures, url parsing ...
if action == "" {
action = ActionUndetected
@@ -168,12 +174,12 @@ func (m *Manager) Send(ctx *fiber.Ctx, err error, action string, count int64, st
}
// increment increments the key by one
func (m *Manager) increment(key string, tags ...Tag) {
func (m *manager) increment(key string, tags ...Tag) {
m.add(key, 1, tags...)
}
// add adds value to key
func (m *Manager) add(key string, value int64, tags ...Tag) {
func (m *manager) add(key string, value int64, tags ...Tag) {
if m.ctx.Err() != nil {
return
}
@@ -192,7 +198,7 @@ func (m *Manager) add(key string, value int64, tags ...Tag) {
}
// Close closes metrics channels, waits for data to complete, closes all plugins
func (m *Manager) Close() {
func (m *manager) Close() {
// drain the datapoint channels
close(m.addDataChan)
m.wg.Wait()
@@ -209,7 +215,7 @@ type publisher interface {
Close()
}
func (m *Manager) addForwarder(addChan <-chan datapoint) {
func (m *manager) addForwarder(addChan <-chan datapoint) {
for data := range addChan {
for _, s := range m.publishers {
s.Add(data.key, data.value, data.tags...)

View File

@@ -23,11 +23,38 @@ import (
"github.com/stretchr/testify/assert"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/s3api/utils"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3log"
"github.com/versity/versitygw/s3response"
)
func TestNewAdminController(t *testing.T) {
type args struct {
iam auth.IAMService
be backend.Backend
l s3log.AuditLogger
}
tests := []struct {
name string
args args
want AdminController
}{
{
name: "initialize admin api",
args: args{},
want: AdminController{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewAdminController(tt.args.iam, tt.args.be, tt.args.l)
assert.Equal(t, got, tt.want)
})
}
}
func TestAdminController_CreateUser(t *testing.T) {
validBody, err := xml.Marshal(auth.Account{
Access: "access",

File diff suppressed because it is too large Load Diff

View File

@@ -16,17 +16,27 @@ package controllers
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"path"
"strings"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"github.com/valyala/fasthttp"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/metrics"
"github.com/versity/versitygw/s3api/utils"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3event"
"github.com/versity/versitygw/s3log"
"github.com/versity/versitygw/s3response"
)
var (
@@ -143,8 +153,572 @@ func buildRequest(bucket, object string, body []byte, headers, queries map[strin
// set the request headers
for key, val := range headers {
req.Header.Add(key, val)
req.Header.Set(key, val)
}
return req
}
func TestNew(t *testing.T) {
type args struct {
be backend.Backend
iam auth.IAMService
logger s3log.AuditLogger
evs s3event.S3EventSender
mm metrics.Manager
debug bool
readonly bool
}
tests := []struct {
name string
args args
want S3ApiController
}{
{
name: "debug enabled",
args: args{
debug: true,
},
want: S3ApiController{
debug: true,
},
},
{
name: "debug disabled",
args: args{
debug: false,
},
want: S3ApiController{
debug: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := New(tt.args.be, tt.args.iam, tt.args.logger, tt.args.evs, tt.args.mm, tt.args.debug, tt.args.readonly)
assert.Equal(t, got, tt.want)
})
}
}
func TestS3ApiController_HandleUnmatch(t *testing.T) {
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "return method not allowed",
output: testOutput{
response: &Response{},
err: s3err.GetAPIError(s3err.ErrMethodNotAllowed),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := S3ApiController{}
testController(
t,
ctrl.HandleUnmatch,
tt.output.response,
tt.output.err,
ctxInputs{})
})
}
}
func TestSetResponseHeaders(t *testing.T) {
type args struct {
headers map[string]*string
}
tests := []struct {
name string
args args
expected map[string]string
}{
{
name: "should not set if map is nil",
args: args{
headers: nil,
},
expected: nil,
},
{
name: "should set some headers",
args: args{
headers: map[string]*string{
"x-amz-checksum-algorithm": utils.GetStringPtr("crc32"),
"x-amz-meta-key": utils.GetStringPtr("meta_key"),
"x-amz-mp-size": utils.GetStringPtr(""),
"something": nil,
},
},
expected: map[string]string{
"x-amz-checksum-algorithm": "crc32",
"x-amz-meta-key": "meta_key",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := fiber.New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
SetResponseHeaders(ctx, tt.args.headers)
if tt.expected != nil {
for key, val := range tt.expected {
v := ctx.Response().Header.Peek(key)
assert.Equal(t, val, string(v))
}
}
})
}
}
// mock the audit logger
type mockAuditLogger struct {
}
func (m *mockAuditLogger) Log(_ *fiber.Ctx, _ error, _ []byte, _ s3log.LogMeta) {}
func (m *mockAuditLogger) HangUp() error { return nil }
func (m *mockAuditLogger) Shutdown() error { return nil }
// mock S3 event sender
type mockEvSender struct {
}
func (m *mockEvSender) SendEvent(_ *fiber.Ctx, _ s3event.EventMeta) {}
func (m *mockEvSender) Close() error { return nil }
// mock metrics manager
type mockMetricsManager struct{}
func (m *mockMetricsManager) Send(_ *fiber.Ctx, _ error, _ string, _ int64, _ int) {}
func (m *mockMetricsManager) Close() {}
func TestProcessController(t *testing.T) {
payload, err := xml.Marshal(s3response.Bucket{
Name: "something",
})
assert.NoError(t, err)
payloadLen := len(payload) + len(xmlhdr)
services := &Services{
Logger: &mockAuditLogger{},
EventSender: &mockEvSender{},
MetricsManager: &mockMetricsManager{},
}
type args struct {
controller Controller
svc *Services
}
type expected struct {
status int
headers map[string]string
body []byte
}
tests := []struct {
name string
args args
expected expected
}{
{
name: "no services successfull response",
args: args{
svc: &Services{},
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{}, nil
},
},
expected: expected{
status: http.StatusOK,
},
},
{
name: "handle api error",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{}, s3err.GetAPIError(s3err.ErrInvalidRequest)
},
},
expected: expected{
status: http.StatusBadRequest,
body: s3err.GetAPIErrorResponse(s3err.GetAPIError(s3err.ErrInvalidRequest), "", "", ""),
},
},
{
name: "handle custom error",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{}, errors.New("custom error")
},
},
expected: expected{
status: http.StatusInternalServerError,
body: s3err.GetAPIErrorResponse(s3err.GetAPIError(s3err.ErrInternalError), "", "", ""),
},
},
{
name: "body parsing fails",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{
Data: make(chan int),
}, nil
},
},
expected: expected{
status: http.StatusInternalServerError,
body: s3err.GetAPIErrorResponse(s3err.GetAPIError(s3err.ErrInternalError), "", "", ""),
},
},
{
name: "no data payload",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{
MetaOpts: &MetaOptions{
ObjectCount: 2,
},
}, nil
},
},
expected: expected{
status: http.StatusOK,
},
},
{
name: "should return 204 http status",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{
MetaOpts: &MetaOptions{
Status: http.StatusNoContent,
},
}, nil
},
},
expected: expected{
status: http.StatusNoContent,
},
},
{
name: "already encoded payload",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{
Data: []byte("encoded_data"),
}, nil
},
},
expected: expected{
status: http.StatusOK,
body: []byte("encoded_data"),
headers: map[string]string{
"Content-Length": "12",
},
},
},
{
name: "should set response headers",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{
Headers: map[string]*string{
"X-Amz-My-Custom-Header": utils.GetStringPtr("my_value"),
"X-Amz-Meta-My-Meta": utils.GetStringPtr("my_meta"),
},
}, nil
},
},
expected: expected{
status: http.StatusOK,
headers: map[string]string{
"X-Amz-My-Custom-Header": "my_value",
"X-Amz-Meta-My-Meta": "my_meta",
},
},
},
{
name: "large paylod: should return internal error",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
type Item struct {
Value string `xml:"value"`
}
type payload struct {
Items []Item `xml:"item"`
}
const targetSize = 5 * 1024 * 1024 // 5 MiB
const itemCount = 500
const valueSize = targetSize / itemCount
p := payload{
Items: make([]Item, itemCount),
}
// Preallocate one shared string of desired size
var sb strings.Builder
sb.Grow(valueSize)
for range valueSize {
sb.WriteByte('A')
}
largeValue := sb.String()
for i := range p.Items {
p.Items[i].Value = largeValue
}
return &Response{
Data: p,
}, nil
},
},
expected: expected{
body: s3err.GetAPIErrorResponse(s3err.GetAPIError(s3err.ErrInternalError), "", "", ""),
status: http.StatusInternalServerError,
},
},
{
name: "not encoded payload",
args: args{
svc: services,
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{
Data: s3response.Bucket{
Name: "something",
},
}, nil
},
},
expected: expected{
headers: map[string]string{
"Content-Length": fmt.Sprint(payloadLen),
},
body: append(xmlhdr, payload...),
status: http.StatusOK,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := fiber.New().AcquireCtx(&fasthttp.RequestCtx{})
err := ProcessController(ctx, tt.args.controller, metrics.ActionAbortMultipartUpload, tt.args.svc)
assert.NoError(t, err)
// check the status
assert.Equal(t, tt.expected.status, ctx.Response().StatusCode())
// check the response headers to be set
if tt.expected.headers != nil {
for key, val := range tt.expected.headers {
v := ctx.Response().Header.Peek(key)
assert.Equal(t, val, string(v))
}
}
// check the response body
if tt.expected.body != nil {
assert.Equal(t, tt.expected.body, ctx.Response().Body())
}
})
}
}
func TestProcessHandlers(t *testing.T) {
payload, err := xml.Marshal(s3response.Checksum{
CRC32: utils.GetStringPtr("crc32"),
})
assert.NoError(t, err)
type args struct {
controller Controller
svc *Services
handlers []fiber.Handler
locals map[utils.ContextKey]any
}
type expected struct {
body []byte
}
tests := []struct {
name string
args args
expected expected
}{
{
name: "should skip the handlers",
args: args{
locals: map[utils.ContextKey]any{
utils.ContextKeySkip: true,
},
},
},
{
name: "handler returns error",
args: args{
handlers: []fiber.Handler{
func(ctx *fiber.Ctx) error {
return nil
},
func(ctx *fiber.Ctx) error {
return s3err.GetAPIError(s3err.ErrAccessDenied)
},
},
svc: &Services{},
},
expected: expected{
body: s3err.GetAPIErrorResponse(s3err.GetAPIError(s3err.ErrAccessDenied), "", "", ""),
},
},
{
name: "should process the controller",
args: args{
handlers: []fiber.Handler{
func(ctx *fiber.Ctx) error {
return nil
},
func(ctx *fiber.Ctx) error {
return nil
},
},
svc: &Services{},
controller: func(ctx *fiber.Ctx) (*Response, error) {
return &Response{
Data: s3response.Checksum{
CRC32: utils.GetStringPtr("crc32"),
},
}, nil
},
},
expected: expected{
body: append(xmlhdr, payload...),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mdlwr := ProcessHandlers(tt.args.controller, metrics.ActionCreateBucket, tt.args.svc, tt.args.handlers...)
app := fiber.New()
app.Post("/:bucket/*", func(ctx *fiber.Ctx) error {
// set the request locals
if tt.args.locals != nil {
for key, val := range tt.args.locals {
key.Set(ctx, val)
}
}
// call the controller by passing the ctx
err := mdlwr(ctx)
assert.NoError(t, err)
// check the response body
if tt.expected.body != nil {
assert.Equal(t, tt.expected.body, ctx.Response().Body())
}
return nil
})
app.All("*", func(ctx *fiber.Ctx) error {
return nil
})
req := buildRequest("bucket", "object", nil, nil, nil)
_, err := app.Test(req)
assert.NoError(t, err)
})
}
}
func TestWrapMiddleware(t *testing.T) {
type args struct {
handler fiber.Handler
logger s3log.AuditLogger
mm metrics.Manager
}
type expected struct {
body []byte
}
tests := []struct {
name string
args args
expected expected
}{
{
name: "handler returns no error",
args: args{
handler: func(ctx *fiber.Ctx) error {
return nil
},
},
},
{
name: "handler returns api error",
args: args{
handler: func(ctx *fiber.Ctx) error {
return s3err.GetAPIError(s3err.ErrAclNotSupported)
},
mm: &mockMetricsManager{},
logger: &mockAuditLogger{},
},
expected: expected{
body: s3err.GetAPIErrorResponse(s3err.GetAPIError(s3err.ErrAclNotSupported), "", "", ""),
},
},
{
name: "handler returns custom error",
args: args{
handler: func(ctx *fiber.Ctx) error {
return errors.New("custom error")
},
},
expected: expected{
body: s3err.GetAPIErrorResponse(s3err.GetAPIError(s3err.ErrInternalError), "", "", ""),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mdlwr := WrapMiddleware(tt.args.handler, tt.args.logger, tt.args.mm)
app := fiber.New()
app.Post("/:bucket/*", func(ctx *fiber.Ctx) error {
// call the controller by passing the ctx
err := mdlwr(ctx)
assert.NoError(t, err)
// check the response body
if tt.expected.body != nil {
assert.Equal(t, tt.expected.body, ctx.Response().Body())
}
return nil
})
app.All("*", func(ctx *fiber.Ctx) error {
return nil
})
req := buildRequest("bucket", "object", nil, nil, nil)
_, err := app.Test(req)
assert.NoError(t, err)
})
}
}

View File

@@ -311,24 +311,7 @@ func (c S3ApiController) PutBucketAcl(ctx *fiber.Ctx) (*Response, error) {
grants := grantFullControl + grantRead + grantReadACP + granWrite + grantWriteACP
var input *auth.PutBucketAclInput
ownership, err := c.be.GetBucketOwnershipControls(ctx.Context(), bucket)
if err != nil && !errors.Is(err, s3err.GetAPIError(s3err.ErrOwnershipControlsNotFound)) {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, err
}
if ownership == types.ObjectOwnershipBucketOwnerEnforced {
debuglogger.Logf("bucket acls are disabled")
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, s3err.GetAPIError(s3err.ErrAclNotSupported)
}
err = auth.VerifyAccess(ctx.Context(), c.be,
err := auth.VerifyAccess(ctx.Context(), c.be,
auth.AccessOptions{
Readonly: c.readonly,
Acl: parsedAcl,
@@ -346,6 +329,23 @@ func (c S3ApiController) PutBucketAcl(ctx *fiber.Ctx) (*Response, error) {
}, err
}
ownership, err := c.be.GetBucketOwnershipControls(ctx.Context(), bucket)
if err != nil && !errors.Is(err, s3err.GetAPIError(s3err.ErrOwnershipControlsNotFound)) {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, err
}
if ownership == types.ObjectOwnershipBucketOwnerEnforced {
debuglogger.Logf("bucket acls are disabled")
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, s3err.GetAPIError(s3err.ErrAclNotSupported)
}
if len(ctx.Body()) > 0 {
var accessControlPolicy auth.AccessControlPolicy
err := xml.Unmarshal(ctx.Body(), &accessControlPolicy)

View File

@@ -17,6 +17,7 @@ package controllers
import (
"context"
"encoding/xml"
"errors"
"fmt"
"net/http"
"testing"
@@ -810,4 +811,291 @@ func TestS3ApiController_CreateBucket(t *testing.T) {
}
}
// TODO: add a test for PutBucketAcl
func TestS3ApiController_PutBucketAcl(t *testing.T) {
invalidBody, err := xml.Marshal(auth.AccessControlPolicy{
Owner: &types.Owner{
ID: utils.GetStringPtr("root"),
},
AccessControlList: auth.AccessControlList{
Grants: []auth.Grant{
{
Permission: auth.Permission("invalid_permission"),
},
},
},
})
assert.NoError(t, err)
incorrectOwnerBody, err := xml.Marshal(auth.AccessControlPolicy{
Owner: &types.Owner{
ID: utils.GetStringPtr("user"),
},
AccessControlList: auth.AccessControlList{},
})
assert.NoError(t, err)
validAccessControlPolicy, err := xml.Marshal(auth.AccessControlPolicy{
Owner: &types.Owner{
ID: utils.GetStringPtr("root"),
},
AccessControlList: auth.AccessControlList{},
})
assert.NoError(t, err)
tests := []struct {
name string
input testInput
output testOutput
}{
{
name: "access denied",
input: testInput{
locals: accessDeniedLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "fails to get bucket ownership",
input: testInput{
locals: defaultLocals,
extraMockErr: s3err.GetAPIError(s3err.ErrInternalError),
extraMockResp: types.ObjectOwnership(""),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrInternalError),
},
},
{
name: "acl not supported",
input: testInput{
locals: defaultLocals,
extraMockResp: types.ObjectOwnershipBucketOwnerEnforced,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrAclNotSupported),
},
},
{
name: "invalid request body",
input: testInput{
locals: defaultLocals,
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
body: []byte("invalid_body"),
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrMalformedACL),
},
},
{
name: "invalid access control policy",
input: testInput{
locals: defaultLocals,
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
body: invalidBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrMalformedACL),
},
},
{
name: "incorrect owner id",
input: testInput{
locals: defaultLocals,
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
body: incorrectOwnerBody,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.APIError{
Code: "InvalidArgument",
Description: "Invalid id",
HTTPStatusCode: http.StatusBadRequest,
},
},
},
{
name: "both access control policy and grants",
input: testInput{
body: validAccessControlPolicy,
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
headers: map[string]string{
"X-Amz-Acl": "public-read",
},
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrUnexpectedContent),
},
},
{
name: "access control policy success",
input: testInput{
body: validAccessControlPolicy,
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
{
name: "invalid canned acl",
input: testInput{
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
headers: map[string]string{
"X-Amz-Acl": "invalid_acl",
},
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrInvalidRequest),
},
},
{
name: "both canned acl and grants",
input: testInput{
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
headers: map[string]string{
"X-Amz-Acl": "public-read",
"X-Amz-Grant-Read": "grt1",
},
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrBothCannedAndHeaderGrants),
},
},
{
name: "canned acl success",
input: testInput{
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
headers: map[string]string{
"X-Amz-Acl": "public-read",
},
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
},
},
{
name: "grants update acl fails",
input: testInput{
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
headers: map[string]string{
"X-Amz-Grant-Read": "grt1",
},
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: errors.New("accounts does not exist: grt1"),
},
},
{
name: "no option provided",
input: testInput{
extraMockResp: types.ObjectOwnershipBucketOwnerPreferred,
locals: defaultLocals,
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrMissingSecurityHeader),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
PutBucketAclFunc: func(contextMoqParam context.Context, bucket string, data []byte) error {
return tt.input.beErr
},
GetBucketOwnershipControlsFunc: func(contextMoqParam context.Context, bucket string) (types.ObjectOwnership, error) {
return tt.input.extraMockResp.(types.ObjectOwnership), tt.input.extraMockErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
}
ctrl := S3ApiController{
be: be,
iam: auth.NewIAMServiceSingle(
auth.Account{
Access: "root",
}),
}
testController(t, ctrl.PutBucketAcl, tt.output.response, tt.output.err,
ctxInputs{
locals: tt.input.locals,
body: tt.input.body,
bucket: tt.input.bucket,
headers: tt.input.headers,
})
})
}
}

View File

@@ -461,7 +461,7 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) {
*res.ContentLength)
return &Response{
MetaOpts: &MetaOptions{
ContentLength: getint64(res.ContentLength),
ContentLength: utils.GetInt64(res.ContentLength),
BucketOwner: parsedAcl.Owner,
Status: status,
},
@@ -501,7 +501,7 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) {
"Last-Modified": utils.FormatDatePtrToString(res.LastModified, timefmt),
},
MetaOpts: &MetaOptions{
ContentLength: getint64(res.ContentLength),
ContentLength: utils.GetInt64(res.ContentLength),
BucketOwner: parsedAcl.Owner,
Status: status,
},

View File

@@ -29,7 +29,7 @@ type S3ApiRouter struct {
WithAdmSrv bool
}
func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, aLogger s3log.AuditLogger, evs s3event.S3EventSender, mm *metrics.Manager, debug bool, readonly bool, region string, root middlewares.RootUserConfig) {
func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, aLogger s3log.AuditLogger, evs s3event.S3EventSender, mm metrics.Manager, debug bool, readonly bool, region string, root middlewares.RootUserConfig) {
ctrl := controllers.New(be, iam, logger, evs, mm, debug, readonly)
adminServices := &controllers.Services{
Logger: aLogger,

View File

@@ -51,7 +51,7 @@ func New(
l s3log.AuditLogger,
adminLogger s3log.AuditLogger,
evs s3event.S3EventSender,
mm *metrics.Manager,
mm metrics.Manager,
opts ...Option,
) (*S3ApiServer, error) {
server := &S3ApiServer{

View File

@@ -704,3 +704,12 @@ func FormatDatePtrToString(date *time.Time, format string) *string {
formatted := date.UTC().Format(format)
return &formatted
}
// GetInt64 returns the value of int64 pointer
func GetInt64(n *int64) int64 {
if n == nil {
return 0
}
return *n
}