diff --git a/.github/workflows/jobs.yaml b/.github/workflows/jobs.yaml index de9c610fa..39213306d 100644 --- a/.github/workflows/jobs.yaml +++ b/.github/workflows/jobs.yaml @@ -1142,7 +1142,7 @@ jobs: result=${result%\%} echo "result:" echo $result - threshold=35.9 + threshold=36.5 if (( $(echo "$result >= $threshold" |bc -l) )); then echo "It is equal or greater than threshold, passed!" else diff --git a/integration/objects_test.go b/integration/objects_test.go new file mode 100644 index 000000000..c24f227f7 --- /dev/null +++ b/integration/objects_test.go @@ -0,0 +1,198 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package integration + +import ( + "context" + "encoding/base64" + "fmt" + "log" + "math/rand" + "net/http" + "strings" + "testing" + "time" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + + "github.com/stretchr/testify/assert" +) + +func TestObjectGet(t *testing.T) { + + // for setup we'll create a bucket and upload a file + endpoint := "localhost:9000" + accessKeyID := "minioadmin" + secretAccessKey := "minioadmin" + + // Initialize minio client object. + minioClient, err := minio.New(endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: false, + }) + if err != nil { + log.Fatalln(err) + } + bucketName := fmt.Sprintf("testbucket-%d", rand.Intn(1000-1)+1) + err = minioClient.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true}) + + if err != nil { + fmt.Println(err) + } + // upload a simple file + fakeFile := "12345678" + fileReader := strings.NewReader(fakeFile) + + _, err = minioClient.PutObject( + context.Background(), + bucketName, + "myobject", fileReader, int64(len(fakeFile)), minio.PutObjectOptions{ContentType: "application/octet-stream"}) + if err != nil { + fmt.Println(err) + return + } + _, err = minioClient.PutObject( + context.Background(), + bucketName, + "myobject.jpg", fileReader, int64(len(fakeFile)), minio.PutObjectOptions{ContentType: "application/octet-stream"}) + if err != nil { + fmt.Println(err) + return + } + + assert := assert.New(t) + type args struct { + encodedPrefix string + versionID string + bytesRange string + } + tests := []struct { + name string + args args + expectedStatus int + expectedError error + }{ + { + name: "Preview Object", + args: args{ + encodedPrefix: base64.StdEncoding.EncodeToString([]byte("myobject")), + }, + expectedStatus: 200, + expectedError: nil, + }, + { + name: "Preview image", + args: args{ + encodedPrefix: base64.StdEncoding.EncodeToString([]byte("myobject.jpg")), + }, + expectedStatus: 200, + expectedError: nil, + }, + { + name: "Get Range of bytes", + args: args{ + encodedPrefix: base64.StdEncoding.EncodeToString([]byte("myobject.jpg")), + bytesRange: "bytes=1-4", + }, + expectedStatus: 206, + expectedError: nil, + }, + { + name: "Get Range of bytes empty start", + args: args{ + encodedPrefix: base64.StdEncoding.EncodeToString([]byte("myobject.jpg")), + bytesRange: "bytes=-4", + }, + expectedStatus: 206, + expectedError: nil, + }, + { + name: "Get Invalid Range of bytes", + args: args{ + encodedPrefix: base64.StdEncoding.EncodeToString([]byte("myobject.jpg")), + bytesRange: "bytes=9-12", + }, + expectedStatus: 400, + expectedError: nil, + }, + { + name: "Get Larger Range of bytes empty start", + args: args{ + encodedPrefix: base64.StdEncoding.EncodeToString([]byte("myobject.jpg")), + bytesRange: "bytes=-12", + }, + expectedStatus: 206, + expectedError: nil, + }, + { + name: "Get invalid seek start Range of bytes", + args: args{ + encodedPrefix: base64.StdEncoding.EncodeToString([]byte("myobject.jpg")), + bytesRange: "bytes=12-16", + }, + expectedStatus: 400, + expectedError: nil, + }, + { + name: "Bad Preview Object", + args: args{ + encodedPrefix: "garble", + }, + expectedStatus: 400, + expectedError: nil, + }, + { + name: "Bad Version Preview Object", + args: args{ + encodedPrefix: base64.StdEncoding.EncodeToString([]byte("myobject")), + versionID: "garble", + }, + expectedStatus: 400, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := &http.Client{ + Timeout: 3 * time.Second, + } + destination := fmt.Sprintf("/api/v1/buckets/%s/objects/download?preview=true&prefix=%s&version_id=%s", bucketName, tt.args.encodedPrefix, tt.args.versionID) + finalURL := fmt.Sprintf("http://localhost:9090%s", destination) + request, err := http.NewRequest("GET", finalURL, nil) + if err != nil { + log.Println(err) + return + } + request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) + request.Header.Add("Content-Type", "application/json") + if tt.args.bytesRange != "" { + request.Header.Add("Range", tt.args.bytesRange) + } + + response, err := client.Do(request) + + assert.NotNil(response, fmt.Sprintf("%s response object is nil", tt.name)) + assert.Nil(err, fmt.Sprintf("%s returned an error: %v", tt.name, err)) + if response != nil { + assert.Equal(tt.expectedStatus, response.StatusCode, fmt.Sprintf("%s returned the wrong status code", tt.name)) + } + }) + } + +} diff --git a/integration/user_api_bucket_test.go b/integration/user_api_bucket_test.go index 2d0f40886..6e20da0d3 100644 --- a/integration/user_api_bucket_test.go +++ b/integration/user_api_bucket_test.go @@ -2799,7 +2799,7 @@ func TestReplication(t *testing.T) { } assert.Greater(len(structBucketRepl.Rules), 0, "Number of expected rules is 0") - if len(structBucketRepl.Rules) == 0 { + if len(structBucketRepl.Rules) == 0 || len(structBucketRepl.Rules) < 3 { return } // 4. Verify rules are enabled diff --git a/restapi/user_objects.go b/restapi/user_objects.go index ee05b1af0..7f4c13741 100644 --- a/restapi/user_objects.go +++ b/restapi/user_objects.go @@ -413,7 +413,11 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo // indicate object size & content type stat, err := resp.Stat() if err != nil { - LogError("Failed to get Stat() response from server for %s: %v", prefix, err) + minErr := minio.ToErrorResponse(err) + // non-200 means we requested something wrong + rw.WriteHeader(minErr.StatusCode) + + LogError("Failed to get Stat() response from server for %s (version %s): %v", prefix, opts.VersionID, minErr.Error()) return } @@ -421,6 +425,7 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo ranges, err := parseRange(params.HTTPRequest.Header.Get("Range"), stat.Size) if err != nil { LogError("Unable to parse range header input %s: %v", params.HTTPRequest.Header.Get("Range"), err) + rw.WriteHeader(400) return } contentType := stat.ContentType @@ -450,6 +455,7 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo _, err = resp.Seek(start, io.SeekStart) if err != nil { LogError("Unable to seek at offset %d: %v", start, err) + rw.WriteHeader(400) return }