diff --git a/integration/action-tests.go b/integration/action-tests.go index 139e969..10265fd 100644 --- a/integration/action-tests.go +++ b/integration/action-tests.go @@ -107,6 +107,7 @@ func TestGetObjectTagging(s *S3Conf) { func TestDeleteObjectTagging(s *S3Conf) { DeleteObjectTagging_non_existing_object(s) + DeleteObjectTagging_success_status(s) DeleteObjectTagging_success(s) } diff --git a/integration/tests.go b/integration/tests.go index e903b0e..b03cbf6 100644 --- a/integration/tests.go +++ b/integration/tests.go @@ -1990,6 +1990,57 @@ func DeleteObjectTagging_non_existing_object(s *S3Conf) { }) } +func DeleteObjectTagging_success_status(s *S3Conf) { + testName := "DeleteObjectTagging_success_status" + actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + obj := "my-obj" + err := putObjects(s3client, []string{obj}, bucket) + if err != nil { + return err + } + + tagging := types.Tagging{ + TagSet: []types.Tag{ + { + Key: getPtr("Hello"), + Value: getPtr("World"), + }, + }, + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ + Bucket: &bucket, + Key: &obj, + Tagging: &tagging, + }) + cancel() + if err != nil { + return err + } + + req, err := createSignedReq(http.MethodDelete, s.endpoint, fmt.Sprintf("%v/%v?tagging", bucket, obj), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now()) + if err != nil { + return err + } + + client := http.Client{ + Timeout: shortTimeout, + } + + resp, err := client.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusNoContent, resp.StatusCode) + } + + return nil + }) +} + func DeleteObjectTagging_success(s *S3Conf) { testName := "DeleteObjectTagging_success" actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { diff --git a/integration/utils.go b/integration/utils.go index d48dc3b..4edd6a6 100644 --- a/integration/utils.go +++ b/integration/utils.go @@ -134,22 +134,9 @@ type authConfig struct { func authHandler(s *S3Conf, cfg *authConfig, handler func(req *http.Request) error) { runF(cfg.testName) - req, err := http.NewRequest(cfg.method, fmt.Sprintf("%v/%v", s.endpoint, cfg.path), bytes.NewReader(cfg.body)) + req, err := createSignedReq(cfg.method, s.endpoint, cfg.path, s.awsID, s.awsSecret, cfg.service, s.awsRegion, cfg.body, cfg.date) if err != nil { - failF("%v: failed to send the request: %v", cfg.testName, err.Error()) - return - } - - signer := v4.NewSigner() - - hashedPayload := sha256.Sum256([]byte{}) - hexPayload := hex.EncodeToString(hashedPayload[:]) - - req.Header.Set("X-Amz-Content-Sha256", hexPayload) - - signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.awsID, SecretAccessKey: s.awsSecret}, req, hexPayload, cfg.service, s.awsRegion, cfg.date) - if signErr != nil { - failF("%v: failed to sign the request: %v", cfg.testName, err.Error()) + failF("%v: %v", cfg.testName, err.Error()) return } @@ -161,6 +148,27 @@ func authHandler(s *S3Conf, cfg *authConfig, handler func(req *http.Request) err passF(cfg.testName) } +func createSignedReq(method, endpoint, path, access, secret, service, region string, body []byte, date time.Time) (*http.Request, error) { + req, err := http.NewRequest(method, fmt.Sprintf("%v/%v", endpoint, path), bytes.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("failed to send the request: %w", err) + } + + signer := v4.NewSigner() + + hashedPayload := sha256.Sum256(body) + hexPayload := hex.EncodeToString(hashedPayload[:]) + + req.Header.Set("X-Amz-Content-Sha256", hexPayload) + + signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: access, SecretAccessKey: secret}, req, hexPayload, service, region, date) + if signErr != nil { + return nil, fmt.Errorf("failed to sign the request: %w", signErr) + } + + return req, nil +} + func checkAuthErr(resp *http.Response, apiErr s3err.APIError) error { body, err := io.ReadAll(resp.Body) if err != nil { diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index 220ed97..087916a 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -708,6 +708,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { err := c.be.RemoveTags(ctx.Context(), bucket, key) return SendResponse(ctx, err, &MetaOpts{ + Status: http.StatusNoContent, Logger: c.logger, EvSender: c.evSender, Action: "RemoveObjectTagging", @@ -957,6 +958,7 @@ type MetaOpts struct { EventName s3event.EventType ObjectETag *string VersionId *string + Status int } func SendResponse(ctx *fiber.Ctx, err error, l *MetaOpts) error { @@ -991,9 +993,12 @@ func SendResponse(ctx *fiber.Ctx, err error, l *MetaOpts) error { utils.LogCtxDetails(ctx, []byte{}) + if l.Status == 0 { + l.Status = http.StatusOK + } // https://github.com/gofiber/fiber/issues/2080 // ctx.SendStatus() sets incorrect content length on HEAD request - ctx.Status(http.StatusOK) + ctx.Status(l.Status) return nil } diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go index ebca82e..c25849a 100644 --- a/s3api/controllers/base_test.go +++ b/s3api/controllers/base_test.go @@ -1065,7 +1065,7 @@ func TestS3ApiController_DeleteActions(t *testing.T) { req: httptest.NewRequest(http.MethodDelete, "/my-bucket/my-key/key2?tagging", nil), }, wantErr: false, - statusCode: 200, + statusCode: 204, }, { name: "Delete-object-success", @@ -1493,6 +1493,7 @@ func Test_response(t *testing.T) { ctx *fiber.Ctx resp any err error + opts *MetaOpts } app := fiber.New() @@ -1510,6 +1511,7 @@ func Test_response(t *testing.T) { ctx: ctx, resp: nil, err: s3err.GetAPIError(s3err.ErrInternalError), + opts: &MetaOpts{}, }, wantErr: false, statusCode: 500, @@ -1520,6 +1522,7 @@ func Test_response(t *testing.T) { ctx: ctx, resp: nil, err: fmt.Errorf("custom error"), + opts: &MetaOpts{}, }, wantErr: false, statusCode: 500, @@ -1530,6 +1533,7 @@ func Test_response(t *testing.T) { ctx: ctx, resp: nil, err: s3err.GetAPIError(s3err.ErrNotImplemented), + opts: &MetaOpts{}, }, wantErr: false, statusCode: 501, @@ -1540,14 +1544,28 @@ func Test_response(t *testing.T) { ctx: ctx, resp: "Valid response", err: nil, + opts: &MetaOpts{}, }, wantErr: false, statusCode: 200, }, + { + name: "Successful-response-status-204", + args: args{ + ctx: ctx, + resp: "Valid response", + err: nil, + opts: &MetaOpts{ + Status: 204, + }, + }, + wantErr: false, + statusCode: 204, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := SendResponse(tt.args.ctx, tt.args.err, &MetaOpts{}); (err != nil) != tt.wantErr { + if err := SendResponse(tt.args.ctx, tt.args.err, tt.args.opts); (err != nil) != tt.wantErr { t.Errorf("response() %v error = %v, wantErr %v", tt.name, err, tt.wantErr) }