From 3cfee3a03281be869b72dc61c38105dc1d560759 Mon Sep 17 00:00:00 2001
From: Jon Austin <62040422+jonaustin09@users.noreply.github.com>
Date: Tue, 6 Jun 2023 08:38:12 -0700
Subject: [PATCH] Utils unit tests (#54)
* fix: Fixed error cases of primitive values
* feat: Added unit test for: DeleteBucket, DeleteObjects, DeleteActions, HeadBucket, HeadObject, CreateActions controllers
* feat: Added unit tests for GetUserMetaData, CreateHttpRequestFromCtx, MarshalStructToXML utility functions
* fix: fixed CreateHttpRequestFromCtx unit test case
---
s3api/controllers/base.go | 6 +-
s3api/controllers/base_test.go | 420 +++++++++++++++++++++++++++------
s3api/utils/utils_test.go | 122 ++++++++++
3 files changed, 481 insertions(+), 67 deletions(-)
create mode 100644 s3api/utils/utils_test.go
diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go
index 222b7ad1..a6138b08 100644
--- a/s3api/controllers/base.go
+++ b/s3api/controllers/base.go
@@ -325,7 +325,11 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
key = strings.Join([]string{key, keyEnd}, "/")
}
- if err := xml.Unmarshal(ctx.Body(), &restoreRequest); err == nil {
+ if ctx.Request().URI().QueryArgs().Has("restore") {
+ xmlErr := xml.Unmarshal(ctx.Body(), &restoreRequest)
+ if xmlErr != nil {
+ return errors.New("wrong api call")
+ }
err := c.be.RestoreObject(bucket, key, &restoreRequest)
return Responce[any](ctx, nil, err)
}
diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go
index 310ffdbe..d19166a6 100644
--- a/s3api/controllers/base_test.go
+++ b/s3api/controllers/base_test.go
@@ -19,9 +19,11 @@ import (
"net/http"
"net/http/httptest"
"reflect"
+ "strings"
"testing"
"github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/gofiber/fiber/v2"
"github.com/valyala/fasthttp"
"github.com/versity/versitygw/backend"
@@ -534,127 +536,413 @@ func TestS3ApiController_PutActions(t *testing.T) {
func TestS3ApiController_DeleteBucket(t *testing.T) {
type args struct {
- ctx *fiber.Ctx
+ req *http.Request
}
+
+ app := fiber.New()
+ s3ApiController := S3ApiController{be: &BackendMock{
+ DeleteBucketFunc: func(bucket string) error {
+ return nil
+ },
+ }}
+
+ app.Delete("/:bucket", s3ApiController.DeleteBucket)
+
+ // error case
+ appErr := fiber.New()
+
+ s3ApiControllerErr := S3ApiController{be: &BackendMock{
+ DeleteBucketFunc: func(bucket string) error {
+ return s3err.GetAPIError(48)
+ },
+ }}
+
+ appErr.Delete("/:bucket", s3ApiControllerErr.DeleteBucket)
+
tests := []struct {
- name string
- c S3ApiController
- args args
- wantErr bool
+ name string
+ app *fiber.App
+ args args
+ wantErr bool
+ statusCode int
}{
- // TODO: Add test cases.
+ {
+ name: "Delete-bucket-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodDelete, "/my-bucket", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Delete-bucket-error",
+ app: appErr,
+ args: args{
+ req: httptest.NewRequest(http.MethodDelete, "/my-bucket", nil),
+ },
+ wantErr: false,
+ statusCode: 400,
+ },
}
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if err := tt.c.DeleteBucket(tt.args.ctx); (err != nil) != tt.wantErr {
- t.Errorf("S3ApiController.DeleteBucket() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
+ resp, err := tt.app.Test(tt.args.req)
+
+ if (err != nil) != tt.wantErr {
+ t.Errorf("S3ApiController.DeleteBucket() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ if resp.StatusCode != tt.statusCode {
+ t.Errorf("S3ApiController.DeleteBucket() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
+ }
}
}
func TestS3ApiController_DeleteObjects(t *testing.T) {
type args struct {
- ctx *fiber.Ctx
+ req *http.Request
}
+
+ app := fiber.New()
+ s3ApiController := S3ApiController{be: &BackendMock{
+ DeleteObjectsFunc: func(bucket string, objects *s3.DeleteObjectsInput) error {
+ return nil
+ },
+ }}
+
+ app.Post("/:bucket", s3ApiController.DeleteObjects)
+
+ // Valid request body
+ xmlBody := `body`
+
+ request := httptest.NewRequest(http.MethodPost, "/my-bucket", strings.NewReader(xmlBody))
+ request.Header.Set("Content-Type", "application/xml")
+
tests := []struct {
- name string
- c S3ApiController
- args args
- wantErr bool
+ name string
+ app *fiber.App
+ args args
+ wantErr bool
+ statusCode int
}{
- // TODO: Add test cases.
+ {
+ name: "Delete-Objects-success",
+ app: app,
+ args: args{
+ req: request,
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Delete-Objects-error",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPost, "/my-bucket", nil),
+ },
+ wantErr: false,
+ statusCode: 500,
+ },
}
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if err := tt.c.DeleteObjects(tt.args.ctx); (err != nil) != tt.wantErr {
- t.Errorf("S3ApiController.DeleteObjects() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
+ resp, err := tt.app.Test(tt.args.req)
+
+ if (err != nil) != tt.wantErr {
+ t.Errorf("S3ApiController.DeleteObjects() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ if resp.StatusCode != tt.statusCode {
+ t.Errorf("S3ApiController.DeleteObjects() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
+ }
}
}
func TestS3ApiController_DeleteActions(t *testing.T) {
type args struct {
- ctx *fiber.Ctx
+ req *http.Request
}
+
+ app := fiber.New()
+ s3ApiController := S3ApiController{be: &BackendMock{
+ DeleteObjectFunc: func(bucket, object string) error {
+ return nil
+ },
+ AbortMultipartUploadFunc: func(*s3.AbortMultipartUploadInput) error {
+ return nil
+ },
+ }}
+
+ app.Delete("/:bucket/:key/*", s3ApiController.DeleteActions)
+
+ //Error case
+ appErr := fiber.New()
+
+ s3ApiControllerErr := S3ApiController{be: &BackendMock{
+ DeleteObjectFunc: func(bucket, object string) error {
+ return s3err.GetAPIError(7)
+ },
+ }}
+
+ appErr.Delete("/:bucket", s3ApiControllerErr.DeleteBucket)
+
tests := []struct {
- name string
- c S3ApiController
- args args
- wantErr bool
+ name string
+ app *fiber.App
+ args args
+ wantErr bool
+ statusCode int
}{
- // TODO: Add test cases.
+ {
+ name: "Abort-multipart-upload-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodDelete, "/my-bucket/my-key?uploadId=324234", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Delete-object-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodDelete, "/my-bucket/my-key", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Delete-object-error",
+ app: appErr,
+ args: args{
+ req: httptest.NewRequest(http.MethodDelete, "/my-bucket/invalid-key", nil),
+ },
+ wantErr: false,
+ statusCode: 404,
+ },
}
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if err := tt.c.DeleteActions(tt.args.ctx); (err != nil) != tt.wantErr {
- t.Errorf("S3ApiController.DeleteActions() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
+ resp, err := tt.app.Test(tt.args.req)
+
+ if (err != nil) != tt.wantErr {
+ t.Errorf("S3ApiController.DeleteActions() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ if resp.StatusCode != tt.statusCode {
+ t.Errorf("S3ApiController.DeleteActions() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
+ }
}
}
func TestS3ApiController_HeadBucket(t *testing.T) {
type args struct {
- ctx *fiber.Ctx
+ req *http.Request
}
+
+ app := fiber.New()
+ s3ApiController := S3ApiController{be: &BackendMock{
+ HeadBucketFunc: func(bucket string) (*s3.HeadBucketOutput, error) {
+ return &s3.HeadBucketOutput{}, nil
+ },
+ }}
+
+ app.Head("/:bucket", s3ApiController.HeadBucket)
+
+ //Error case
+ appErr := fiber.New()
+
+ s3ApiControllerErr := S3ApiController{be: &BackendMock{
+ HeadBucketFunc: func(bucket string) (*s3.HeadBucketOutput, error) {
+ return nil, s3err.GetAPIError(3)
+ },
+ }}
+
+ appErr.Head("/:bucket", s3ApiControllerErr.HeadBucket)
+
tests := []struct {
- name string
- c S3ApiController
- args args
- wantErr bool
+ name string
+ app *fiber.App
+ args args
+ wantErr bool
+ statusCode int
}{
- // TODO: Add test cases.
+ {
+ name: "Head-bucket-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodHead, "/my-bucket", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Head-bucket-error",
+ app: appErr,
+ args: args{
+ req: httptest.NewRequest(http.MethodHead, "/my-bucket", nil),
+ },
+ wantErr: false,
+ statusCode: 409,
+ },
}
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if err := tt.c.HeadBucket(tt.args.ctx); (err != nil) != tt.wantErr {
- t.Errorf("S3ApiController.HeadBucket() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
+ resp, err := tt.app.Test(tt.args.req)
+
+ if (err != nil) != tt.wantErr {
+ t.Errorf("S3ApiController.HeadBucket() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ if resp.StatusCode != tt.statusCode {
+ t.Errorf("S3ApiController.HeadBucket() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
+ }
}
}
func TestS3ApiController_HeadObject(t *testing.T) {
type args struct {
- ctx *fiber.Ctx
+ req *http.Request
}
+
+ app := fiber.New()
+ s3ApiController := S3ApiController{be: &BackendMock{
+ HeadObjectFunc: func(bucket, object string) (*s3.HeadObjectOutput, error) {
+ return nil, nil
+ },
+ }}
+
+ app.Head("/:bucket/:key/*", s3ApiController.HeadObject)
+
+ //Error case
+ appErr := fiber.New()
+
+ s3ApiControllerErr := S3ApiController{be: &BackendMock{
+ HeadObjectFunc: func(bucket, object string) (*s3.HeadObjectOutput, error) {
+ return nil, s3err.GetAPIError(42)
+ },
+ }}
+
+ appErr.Head("/:bucket/:key/*", s3ApiControllerErr.HeadObject)
+
tests := []struct {
- name string
- c S3ApiController
- args args
- wantErr bool
+ name string
+ app *fiber.App
+ args args
+ wantErr bool
+ statusCode int
}{
- // TODO: Add test cases.
+ {
+ name: "Head-object-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodHead, "/my-bucket/my-key", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Head-object-error",
+ app: appErr,
+ args: args{
+ req: httptest.NewRequest(http.MethodHead, "/my-bucket/my-key", nil),
+ },
+ wantErr: false,
+ statusCode: 400,
+ },
}
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if err := tt.c.HeadObject(tt.args.ctx); (err != nil) != tt.wantErr {
- t.Errorf("S3ApiController.HeadObject() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
+ resp, err := tt.app.Test(tt.args.req)
+
+ if (err != nil) != tt.wantErr {
+ t.Errorf("S3ApiController.HeadObject() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ if resp.StatusCode != tt.statusCode {
+ t.Errorf("S3ApiController.HeadObject() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
+ }
}
}
func TestS3ApiController_CreateActions(t *testing.T) {
type args struct {
- ctx *fiber.Ctx
+ req *http.Request
}
+ app := fiber.New()
+ s3ApiController := S3ApiController{be: &BackendMock{
+ RestoreObjectFunc: func(bucket, object string, restoreRequest *s3.RestoreObjectInput) error {
+ return nil
+ },
+ CompleteMultipartUploadFunc: func(bucket, object, uploadID string, parts []types.Part) (*s3.CompleteMultipartUploadOutput, error) {
+ return &s3.CompleteMultipartUploadOutput{}, nil
+ },
+ CreateMultipartUploadFunc: func(*s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) {
+ return &s3.CreateMultipartUploadOutput{}, nil
+ },
+ }}
+
+ app.Post("/:bucket/:key/*", s3ApiController.CreateActions)
+
tests := []struct {
- name string
- c S3ApiController
- args args
- wantErr bool
+ name string
+ app *fiber.App
+ args args
+ wantErr bool
+ statusCode int
}{
- // TODO: Add test cases.
+ {
+ name: "Restore-object-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?restore", strings.NewReader(`body`)),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Restore-object-error",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?restore", nil),
+ },
+ wantErr: false,
+ statusCode: 500,
+ },
+ {
+ name: "Complete-multipart-upload-error",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?uploadId=23423", nil),
+ },
+ wantErr: false,
+ statusCode: 500,
+ },
+ {
+ name: "Complete-multipart-upload-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?uploadId=23423", strings.NewReader(`body`)),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
+ {
+ name: "Create-multipart-upload-success",
+ app: app,
+ args: args{
+ req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key", nil),
+ },
+ wantErr: false,
+ statusCode: 200,
+ },
}
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if err := tt.c.CreateActions(tt.args.ctx); (err != nil) != tt.wantErr {
- t.Errorf("S3ApiController.CreateActions() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
+ resp, err := tt.app.Test(tt.args.req)
+
+ if (err != nil) != tt.wantErr {
+ t.Errorf("S3ApiController.CreateActions() error = %v, wantErr %v", err, tt.wantErr)
+ }
+
+ if resp.StatusCode != tt.statusCode {
+ t.Errorf("S3ApiController.CreateActions() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
+ }
}
}
diff --git a/s3api/utils/utils_test.go b/s3api/utils/utils_test.go
new file mode 100644
index 00000000..c5b11524
--- /dev/null
+++ b/s3api/utils/utils_test.go
@@ -0,0 +1,122 @@
+package utils
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "reflect"
+ "testing"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/valyala/fasthttp"
+)
+
+func TestCreateHttpRequestFromCtx(t *testing.T) {
+ type args struct {
+ ctx *fiber.Ctx
+ }
+
+ app := fiber.New()
+
+ // Expected output, Case 1
+ ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
+ req := ctx.Request()
+ request, _ := http.NewRequest(string(req.Header.Method()), req.URI().String(), bytes.NewReader(req.Body()))
+
+ // Case 2
+ ctx2 := app.AcquireCtx(&fasthttp.RequestCtx{})
+ req2 := ctx2.Request()
+ req2.Header.Add("X-Amz-Mfa", "Some valid Mfa")
+
+ request2, _ := http.NewRequest(string(req2.Header.Method()), req2.URI().String(), bytes.NewReader(req2.Body()))
+ request2.Header.Add("X-Amz-Mfa", "Some valid Mfa")
+
+ tests := []struct {
+ name string
+ args args
+ want *http.Request
+ wantErr bool
+ }{
+ {
+ name: "Success-response",
+ args: args{
+ ctx: ctx,
+ },
+ want: request,
+ wantErr: false,
+ },
+ {
+ name: "Success-response-With-Headers",
+ args: args{
+ ctx: ctx2,
+ },
+ want: request2,
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := CreateHttpRequestFromCtx(tt.args.ctx)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("CreateHttpRequestFromCtx() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+
+ fmt.Println(got.Header, tt.want.Header)
+
+ if !reflect.DeepEqual(got.Header, tt.want.Header) {
+ t.Errorf("CreateHttpRequestFromCtx() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestGetUserMetaData(t *testing.T) {
+ type args struct {
+ headers *fasthttp.RequestHeader
+ }
+
+ app := fiber.New()
+
+ // Case 1
+ ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
+ req := ctx.Request()
+
+ // Case 2
+ ctx2 := app.AcquireCtx(&fasthttp.RequestCtx{})
+ req2 := ctx2.Request()
+
+ req2.Header.Add("X-Amz-Meta-Name", "Nick")
+ req2.Header.Add("X-Amz-Meta-Age", "27")
+
+ tests := []struct {
+ name string
+ args args
+ wantMetadata map[string]string
+ }{
+ {
+ name: "Success-empty-response",
+ args: args{
+ headers: &req.Header,
+ },
+ wantMetadata: map[string]string{},
+ },
+ {
+ name: "Success-non-empty-response",
+ args: args{
+ headers: &req2.Header,
+ },
+ wantMetadata: map[string]string{
+ "Age": "27",
+ "Name": "Nick",
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if gotMetadata := GetUserMetaData(tt.args.headers); !reflect.DeepEqual(gotMetadata, tt.wantMetadata) {
+ t.Errorf("GetUserMetaData() = %v, want %v", gotMetadata, tt.wantMetadata)
+ }
+ })
+ }
+}