From ab571a65710bbbb11e8e273a0c5188023ddd3161 Mon Sep 17 00:00:00 2001 From: niksis02 Date: Sat, 12 Jul 2025 02:00:36 +0400 Subject: [PATCH] feat: implements unit tests for admin controllers --- s3api/controllers/admin.go | 5 + s3api/controllers/admin_test.go | 552 ++++++++++++++++++++++++++++++++ 2 files changed, 557 insertions(+) create mode 100644 s3api/controllers/admin_test.go diff --git a/s3api/controllers/admin.go b/s3api/controllers/admin.go index cbf1942..c4f5f18 100644 --- a/s3api/controllers/admin.go +++ b/s3api/controllers/admin.go @@ -113,6 +113,11 @@ func (c AdminController) UpdateUser(ctx *fiber.Ctx) (*Response, error) { func (c AdminController) DeleteUser(ctx *fiber.Ctx) (*Response, error) { access := ctx.Query("access") + if access == "" { + return &Response{ + MetaOpts: &MetaOptions{}, + }, s3err.GetAPIError(s3err.ErrAdminMissingUserAcess) + } err := c.iam.DeleteUserAccount(access) return &Response{ diff --git a/s3api/controllers/admin_test.go b/s3api/controllers/admin_test.go new file mode 100644 index 0000000..4469620 --- /dev/null +++ b/s3api/controllers/admin_test.go @@ -0,0 +1,552 @@ +// 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" + "errors" + "net/http" + "testing" + + "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 TestAdminController_CreateUser(t *testing.T) { + validBody, err := xml.Marshal(auth.Account{ + Access: "access", + Secret: "secret", + Role: auth.RoleAdmin, + }) + assert.NoError(t, err) + + invalidUserRoleBody, err := xml.Marshal(auth.Account{ + Access: "access", + Secret: "secret", + Role: auth.Role("invalid_role"), + }) + assert.NoError(t, err) + + tests := []struct { + name string + input testInput + output testOutput + }{ + { + name: "invalid request body", + input: testInput{ + body: []byte("invalid_request_body"), + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrMalformedXML), + }, + }, + { + name: "invalid user role", + input: testInput{ + body: invalidUserRoleBody, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrAdminInvalidUserRole), + }, + }, + { + name: "backend returns user exists error", + input: testInput{ + body: validBody, + beErr: auth.ErrUserExists, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrAdminUserExists), + }, + }, + { + name: "backend returns other error", + input: testInput{ + body: validBody, + beErr: s3err.GetAPIError(s3err.ErrInvalidRequest), + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrInvalidRequest), + }, + }, + { + name: "successful response", + input: testInput{ + body: validBody, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{ + Status: http.StatusCreated, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iam := &IAMServiceMock{ + CreateAccountFunc: func(account auth.Account) error { + return tt.input.beErr + }, + } + + ctrl := AdminController{ + iam: iam, + } + + testController( + t, + ctrl.CreateUser, + tt.output.response, + tt.output.err, + ctxInputs{ + body: tt.input.body, + }) + }) + } +} + +func TestAdminController_UpdateUser(t *testing.T) { + validBody, err := xml.Marshal(auth.MutableProps{ + Secret: utils.GetStringPtr("secret"), + Role: auth.RoleAdmin, + }) + assert.NoError(t, err) + + invalidUserRoleBody, err := xml.Marshal(auth.MutableProps{ + Secret: utils.GetStringPtr("secret"), + Role: auth.Role("invalid_role"), + }) + assert.NoError(t, err) + + tests := []struct { + name string + input testInput + output testOutput + }{ + { + name: "missing user access key", + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrAdminMissingUserAcess), + }, + }, + { + name: "invalid request body", + input: testInput{ + body: []byte("invalid_request_body"), + queries: map[string]string{ + "access": "user", + }, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrMalformedXML), + }, + }, + { + name: "invalid user role", + input: testInput{ + body: invalidUserRoleBody, + queries: map[string]string{ + "access": "user", + }, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrAdminInvalidUserRole), + }, + }, + { + name: "backend returns user not found error", + input: testInput{ + body: validBody, + beErr: auth.ErrNoSuchUser, + queries: map[string]string{ + "access": "user", + }, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrAdminUserNotFound), + }, + }, + { + name: "backend returns other error", + input: testInput{ + body: validBody, + beErr: s3err.GetAPIError(s3err.ErrInvalidRequest), + queries: map[string]string{ + "access": "user", + }, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrInvalidRequest), + }, + }, + { + name: "successful response", + input: testInput{ + body: validBody, + queries: map[string]string{ + "access": "user", + }, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iam := &IAMServiceMock{ + UpdateUserAccountFunc: func(access string, props auth.MutableProps) error { + return tt.input.beErr + }, + } + + ctrl := AdminController{ + iam: iam, + } + + testController( + t, + ctrl.UpdateUser, + tt.output.response, + tt.output.err, + ctxInputs{ + body: tt.input.body, + queries: tt.input.queries, + }) + }) + } +} + +func TestAdminController_DeleteUser(t *testing.T) { + tests := []struct { + name string + input testInput + output testOutput + }{ + { + name: "missing user access key", + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrAdminMissingUserAcess), + }, + }, + { + name: "backend returns other error", + input: testInput{ + beErr: s3err.GetAPIError(s3err.ErrInvalidRequest), + queries: map[string]string{ + "access": "user", + }, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrInvalidRequest), + }, + }, + { + name: "successful response", + input: testInput{ + queries: map[string]string{ + "access": "user", + }, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iam := &IAMServiceMock{ + DeleteUserAccountFunc: func(access string) error { + return tt.input.beErr + }, + } + + ctrl := AdminController{ + iam: iam, + } + + testController( + t, + ctrl.DeleteUser, + tt.output.response, + tt.output.err, + ctxInputs{ + queries: tt.input.queries, + }) + }) + } +} + +func TestAdminController_ListUsers(t *testing.T) { + accs := []auth.Account{ + { + Access: "access", + Secret: "secret", + }, + { + Access: "access", + Secret: "secret", + }, + } + tests := []struct { + name string + input testInput + output testOutput + }{ + { + name: "backend returns error", + input: testInput{ + beRes: []auth.Account{}, + beErr: s3err.GetAPIError(s3err.ErrInternalError), + }, + output: testOutput{ + response: &Response{ + Data: auth.ListUserAccountsResult{ + Accounts: []auth.Account{}, + }, + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrInternalError), + }, + }, + { + name: "successful response", + input: testInput{ + beRes: accs, + }, + output: testOutput{ + response: &Response{ + Data: auth.ListUserAccountsResult{ + Accounts: accs, + }, + MetaOpts: &MetaOptions{}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iam := &IAMServiceMock{ + ListUserAccountsFunc: func() ([]auth.Account, error) { + return tt.input.beRes.([]auth.Account), tt.input.beErr + }, + } + + ctrl := AdminController{ + iam: iam, + } + + testController( + t, + ctrl.ListUsers, + tt.output.response, + tt.output.err, + ctxInputs{ + queries: tt.input.queries, + }) + }) + } +} + +func TestAdminController_ChangeBucketOwner(t *testing.T) { + tests := []struct { + name string + input testInput + output testOutput + }{ + { + name: "fails to get user account", + input: testInput{ + extraMockErr: s3err.GetAPIError(s3err.ErrInternalError), + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: errors.New("check user account: "), + }, + }, + { + name: "user not found", + input: testInput{ + extraMockErr: auth.ErrNoSuchUser, + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrAdminUserNotFound), + }, + }, + { + name: "backend returns error", + input: testInput{ + beErr: s3err.GetAPIError(s3err.ErrAdminMethodNotSupported), + }, + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrAdminMethodNotSupported), + }, + }, + { + name: "successful response", + output: testOutput{ + response: &Response{ + MetaOpts: &MetaOptions{}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iam := &IAMServiceMock{ + GetUserAccountFunc: func(access string) (auth.Account, error) { + return auth.Account{}, tt.input.extraMockErr + }, + } + be := &BackendMock{ + ChangeBucketOwnerFunc: func(contextMoqParam context.Context, bucket string, acl []byte) error { + return tt.input.beErr + }, + } + + ctrl := AdminController{ + iam: iam, + be: be, + } + + testController( + t, + ctrl.ChangeBucketOwner, + tt.output.response, + tt.output.err, + ctxInputs{}, + ) + }) + } +} + +func TestAdminController_ListBuckets(t *testing.T) { + res := []s3response.Bucket{ + { + Name: "bucket", + Owner: "owner", + }, + } + + tests := []struct { + name string + input testInput + output testOutput + }{ + { + name: "backend returns other error", + input: testInput{ + beRes: []s3response.Bucket{}, + beErr: s3err.GetAPIError(s3err.ErrNoSuchBucket), + }, + output: testOutput{ + response: &Response{ + Data: s3response.ListBucketsResult{ + Buckets: []s3response.Bucket{}, + }, + MetaOpts: &MetaOptions{}, + }, + err: s3err.GetAPIError(s3err.ErrNoSuchBucket), + }, + }, + { + name: "successful response", + input: testInput{ + beRes: res, + }, + output: testOutput{ + response: &Response{ + Data: s3response.ListBucketsResult{ + Buckets: res, + }, + MetaOpts: &MetaOptions{}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + be := &BackendMock{ + ListBucketsAndOwnersFunc: func(contextMoqParam context.Context) ([]s3response.Bucket, error) { + return tt.input.beRes.([]s3response.Bucket), tt.input.beErr + }, + } + + ctrl := AdminController{ + be: be, + } + + testController( + t, + ctrl.ListBuckets, + tt.output.response, + tt.output.err, + ctxInputs{}, + ) + }) + } +}