87c05eb514
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
590 lines
18 KiB
Go
590 lines
18 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/xml"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api"
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/data"
|
|
apiErrors "github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const defaultURL = "http://localhost/"
|
|
|
|
func TestFormObjectLock(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
bktInfo *data.BucketInfo
|
|
config *data.ObjectLockConfiguration
|
|
header http.Header
|
|
expectedError bool
|
|
expectedLock *data.ObjectLock
|
|
}{
|
|
{
|
|
name: "default days",
|
|
bktInfo: &data.BucketInfo{ObjectLockEnabled: true},
|
|
config: &data.ObjectLockConfiguration{Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{Mode: complianceMode, Days: 1}}},
|
|
expectedLock: &data.ObjectLock{Retention: &data.RetentionLock{
|
|
IsCompliance: true,
|
|
Until: time.Now().Add(24 * time.Hour)}},
|
|
},
|
|
{
|
|
name: "default years",
|
|
bktInfo: &data.BucketInfo{ObjectLockEnabled: true},
|
|
config: &data.ObjectLockConfiguration{Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{Mode: governanceMode, Years: 1}}},
|
|
expectedLock: &data.ObjectLock{Retention: &data.RetentionLock{
|
|
Until: time.Now().Add(365 * 24 * time.Hour)}},
|
|
},
|
|
{
|
|
name: "basic override",
|
|
bktInfo: &data.BucketInfo{ObjectLockEnabled: true},
|
|
config: &data.ObjectLockConfiguration{Rule: &data.ObjectLockRule{DefaultRetention: &data.DefaultRetention{Mode: complianceMode, Days: 1}}},
|
|
header: map[string][]string{
|
|
api.AmzObjectLockRetainUntilDate: {time.Now().Format(time.RFC3339)},
|
|
api.AmzObjectLockMode: {governanceMode},
|
|
api.AmzObjectLockLegalHold: {legalHoldOn},
|
|
},
|
|
expectedLock: &data.ObjectLock{
|
|
LegalHold: &data.LegalHoldLock{Enabled: true},
|
|
Retention: &data.RetentionLock{Until: time.Now()}},
|
|
},
|
|
{
|
|
name: "lock disabled error",
|
|
bktInfo: &data.BucketInfo{},
|
|
header: map[string][]string{api.AmzObjectLockLegalHold: {legalHoldOn}},
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "invalid time format error",
|
|
bktInfo: &data.BucketInfo{ObjectLockEnabled: true},
|
|
header: map[string][]string{
|
|
api.AmzObjectLockRetainUntilDate: {time.Now().Format(time.RFC822)},
|
|
},
|
|
expectedError: true,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actualObjLock, err := formObjectLock(tc.bktInfo, tc.config, tc.header)
|
|
if tc.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assertObjectLocks(t, tc.expectedLock, actualObjLock)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFormObjectLockFromRetention(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
retention *data.Retention
|
|
header http.Header
|
|
expectedError bool
|
|
expectedLock *data.ObjectLock
|
|
}{
|
|
{
|
|
name: "basic compliance",
|
|
retention: &data.Retention{
|
|
Mode: complianceMode,
|
|
RetainUntilDate: time.Now().Format(time.RFC3339),
|
|
},
|
|
expectedLock: &data.ObjectLock{Retention: &data.RetentionLock{
|
|
Until: time.Now(),
|
|
IsCompliance: true}},
|
|
},
|
|
{
|
|
name: "basic governance",
|
|
retention: &data.Retention{
|
|
Mode: governanceMode,
|
|
RetainUntilDate: time.Now().Format(time.RFC3339),
|
|
},
|
|
header: map[string][]string{
|
|
api.AmzBypassGovernanceRetention: {strconv.FormatBool(true)},
|
|
},
|
|
expectedLock: &data.ObjectLock{Retention: &data.RetentionLock{Until: time.Now()}},
|
|
},
|
|
{
|
|
name: "error invalid mode",
|
|
retention: &data.Retention{
|
|
Mode: "",
|
|
RetainUntilDate: time.Now().Format(time.RFC3339),
|
|
},
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "error invalid date",
|
|
retention: &data.Retention{
|
|
Mode: governanceMode,
|
|
RetainUntilDate: "",
|
|
},
|
|
expectedError: true,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actualObjLock, err := formObjectLockFromRetention(tc.retention, tc.header)
|
|
if tc.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assertObjectLocks(t, tc.expectedLock, actualObjLock)
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertObjectLocks(t *testing.T, expected, actual *data.ObjectLock) {
|
|
require.Equal(t, expected.LegalHold, actual.LegalHold)
|
|
if expected.Retention != nil {
|
|
require.Equal(t, expected.Retention.IsCompliance, actual.Retention.IsCompliance)
|
|
require.InDelta(t, expected.Retention.Until.Unix(), actual.Retention.Until.Unix(), 1)
|
|
}
|
|
}
|
|
|
|
func TestLockConfiguration(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
configuration *data.ObjectLockConfiguration
|
|
expectedError bool
|
|
}{
|
|
{
|
|
name: "basic empty",
|
|
configuration: &data.ObjectLockConfiguration{},
|
|
},
|
|
{
|
|
name: "basic compliance",
|
|
configuration: &data.ObjectLockConfiguration{
|
|
ObjectLockEnabled: enabledValue,
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Days: 1,
|
|
Mode: complianceMode,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "basic governance",
|
|
configuration: &data.ObjectLockConfiguration{
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Mode: governanceMode,
|
|
Years: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "error invalid enabled",
|
|
configuration: &data.ObjectLockConfiguration{
|
|
ObjectLockEnabled: "false",
|
|
},
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "error invalid mode",
|
|
configuration: &data.ObjectLockConfiguration{
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Mode: "",
|
|
},
|
|
},
|
|
},
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "error no duration",
|
|
configuration: &data.ObjectLockConfiguration{
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Mode: governanceMode,
|
|
},
|
|
},
|
|
},
|
|
expectedError: true,
|
|
},
|
|
{
|
|
name: "error both durations",
|
|
configuration: &data.ObjectLockConfiguration{
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Days: 1,
|
|
Mode: governanceMode,
|
|
Years: 1,
|
|
},
|
|
},
|
|
},
|
|
expectedError: true,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := checkLockConfiguration(tc.configuration)
|
|
if tc.expectedError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPutBucketLockConfigurationHandler(t *testing.T) {
|
|
ctx := context.Background()
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktLockDisabled := "bucket-lock-disabled"
|
|
createTestBucket(ctx, t, hc, bktLockDisabled)
|
|
|
|
bktLockEnabled := "bucket-lock-enabled"
|
|
createTestBucketWithLock(ctx, t, hc, bktLockEnabled, nil)
|
|
|
|
bktLockEnabledWithOldConfig := "bucket-lock-enabled-old-conf"
|
|
createTestBucketWithLock(ctx, t, hc, bktLockEnabledWithOldConfig,
|
|
&data.ObjectLockConfiguration{
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Days: 1,
|
|
Mode: complianceMode,
|
|
},
|
|
},
|
|
})
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
bucket string
|
|
expectedError apiErrors.Error
|
|
noError bool
|
|
configuration *data.ObjectLockConfiguration
|
|
}{
|
|
{
|
|
name: "bkt not found",
|
|
expectedError: apiErrors.GetAPIError(apiErrors.ErrNoSuchBucket),
|
|
},
|
|
{
|
|
name: "bkt lock disabled",
|
|
bucket: bktLockDisabled,
|
|
expectedError: apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotAllowed),
|
|
},
|
|
{
|
|
name: "invalid configuration",
|
|
bucket: bktLockEnabled,
|
|
expectedError: apiErrors.GetAPIError(apiErrors.ErrInternalError),
|
|
configuration: &data.ObjectLockConfiguration{ObjectLockEnabled: "dummy"},
|
|
},
|
|
{
|
|
name: "basic",
|
|
bucket: bktLockEnabled,
|
|
noError: true,
|
|
configuration: &data.ObjectLockConfiguration{},
|
|
},
|
|
{
|
|
name: "basic override",
|
|
bucket: bktLockEnabledWithOldConfig,
|
|
noError: true,
|
|
configuration: &data.ObjectLockConfiguration{
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Mode: governanceMode,
|
|
Years: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
body, err := xml.Marshal(tc.configuration)
|
|
require.NoError(t, err)
|
|
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(body))
|
|
r = r.WithContext(api.SetReqInfo(r.Context(), api.NewReqInfo(w, r, api.ObjectRequest{Bucket: tc.bucket})))
|
|
|
|
hc.Handler().PutBucketObjectLockConfigHandler(w, r)
|
|
|
|
if !tc.noError {
|
|
assertS3Error(t, w, tc.expectedError)
|
|
return
|
|
}
|
|
|
|
bktInfo, err := hc.Layer().GetBucketInfo(ctx, tc.bucket)
|
|
require.NoError(t, err)
|
|
bktSettings, err := hc.Layer().GetBucketSettings(ctx, bktInfo)
|
|
require.NoError(t, err)
|
|
actualConf := bktSettings.LockConfiguration
|
|
require.True(t, bktSettings.VersioningEnabled())
|
|
require.Equal(t, tc.configuration.ObjectLockEnabled, actualConf.ObjectLockEnabled)
|
|
require.Equal(t, tc.configuration.Rule, actualConf.Rule)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetBucketLockConfigurationHandler(t *testing.T) {
|
|
ctx := context.Background()
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktLockDisabled := "bucket-lock-disabled"
|
|
createTestBucket(ctx, t, hc, bktLockDisabled)
|
|
|
|
bktLockEnabled := "bucket-lock-enabled"
|
|
createTestBucketWithLock(ctx, t, hc, bktLockEnabled, nil)
|
|
|
|
oldConfig := &data.ObjectLockConfiguration{
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Days: 1,
|
|
Mode: complianceMode,
|
|
},
|
|
},
|
|
}
|
|
bktLockEnabledWithOldConfig := "bucket-lock-enabled-old-conf"
|
|
createTestBucketWithLock(ctx, t, hc, bktLockEnabledWithOldConfig, oldConfig)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
bucket string
|
|
expectedError apiErrors.Error
|
|
noError bool
|
|
expectedConf *data.ObjectLockConfiguration
|
|
}{
|
|
{
|
|
name: "bkt not found",
|
|
expectedError: apiErrors.GetAPIError(apiErrors.ErrNoSuchBucket),
|
|
},
|
|
{
|
|
name: "bkt lock disabled",
|
|
bucket: bktLockDisabled,
|
|
expectedError: apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound),
|
|
},
|
|
{
|
|
name: "bkt lock enabled empty default",
|
|
bucket: bktLockEnabled,
|
|
noError: true,
|
|
expectedConf: &data.ObjectLockConfiguration{ObjectLockEnabled: enabledValue},
|
|
},
|
|
{
|
|
name: "bkt lock enabled",
|
|
bucket: bktLockEnabledWithOldConfig,
|
|
noError: true,
|
|
expectedConf: oldConfig,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(nil))
|
|
r = r.WithContext(api.SetReqInfo(r.Context(), api.NewReqInfo(w, r, api.ObjectRequest{Bucket: tc.bucket})))
|
|
|
|
hc.Handler().GetBucketObjectLockConfigHandler(w, r)
|
|
|
|
if !tc.noError {
|
|
assertS3Error(t, w, tc.expectedError)
|
|
return
|
|
}
|
|
|
|
actualConf := &data.ObjectLockConfiguration{}
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualConf)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tc.expectedConf.ObjectLockEnabled, actualConf.ObjectLockEnabled)
|
|
require.Equal(t, tc.expectedConf.Rule, actualConf.Rule)
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertS3Error(t *testing.T, w *httptest.ResponseRecorder, expectedError apiErrors.Error) {
|
|
actualErrorResponse := &api.ErrorResponse{}
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualErrorResponse)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, expectedError.HTTPStatusCode, w.Code)
|
|
require.Equal(t, expectedError.Code, actualErrorResponse.Code)
|
|
|
|
if expectedError.ErrCode != apiErrors.ErrInternalError {
|
|
require.Equal(t, expectedError.Description, actualErrorResponse.Message)
|
|
}
|
|
}
|
|
|
|
func TestObjectLegalHold(t *testing.T) {
|
|
ctx := context.Background()
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-lock-enabled"
|
|
bktInfo := createTestBucketWithLock(ctx, t, hc, bktName, nil)
|
|
|
|
objName := "obj-for-legal-hold"
|
|
createTestObject(ctx, t, hc, bktInfo, objName)
|
|
|
|
w, r := prepareTestRequest(t, bktName, objName, nil)
|
|
hc.Handler().GetObjectLegalHoldHandler(w, r)
|
|
assertLegalHold(t, w, legalHoldOff)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objName, &data.LegalHold{Status: legalHoldOn})
|
|
hc.Handler().PutObjectLegalHoldHandler(w, r)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objName, nil)
|
|
hc.Handler().GetObjectLegalHoldHandler(w, r)
|
|
assertLegalHold(t, w, legalHoldOn)
|
|
|
|
// to make sure put hold is an idempotent operation
|
|
w, r = prepareTestRequest(t, bktName, objName, &data.LegalHold{Status: legalHoldOn})
|
|
hc.Handler().PutObjectLegalHoldHandler(w, r)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objName, &data.LegalHold{Status: legalHoldOff})
|
|
hc.Handler().PutObjectLegalHoldHandler(w, r)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objName, nil)
|
|
hc.Handler().GetObjectLegalHoldHandler(w, r)
|
|
assertLegalHold(t, w, legalHoldOff)
|
|
|
|
// to make sure put hold is an idempotent operation
|
|
w, r = prepareTestRequest(t, bktName, objName, &data.LegalHold{Status: legalHoldOff})
|
|
hc.Handler().PutObjectLegalHoldHandler(w, r)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func assertLegalHold(t *testing.T, w *httptest.ResponseRecorder, status string) {
|
|
actualHold := &data.LegalHold{}
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualHold)
|
|
require.NoError(t, err)
|
|
require.Equal(t, status, actualHold.Status)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func TestObjectRetention(t *testing.T) {
|
|
ctx := context.Background()
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-lock-enabled"
|
|
bktInfo := createTestBucketWithLock(ctx, t, hc, bktName, nil)
|
|
|
|
objName := "obj-for-retention"
|
|
createTestObject(ctx, t, hc, bktInfo, objName)
|
|
|
|
w, r := prepareTestRequest(t, bktName, objName, nil)
|
|
hc.Handler().GetObjectRetentionHandler(w, r)
|
|
assertS3Error(t, w, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey))
|
|
|
|
retention := &data.Retention{Mode: governanceMode, RetainUntilDate: time.Now().UTC().Format(time.RFC3339)}
|
|
w, r = prepareTestRequest(t, bktName, objName, retention)
|
|
hc.Handler().PutObjectRetentionHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objName, nil)
|
|
hc.Handler().GetObjectRetentionHandler(w, r)
|
|
assertRetention(t, w, retention)
|
|
|
|
retention = &data.Retention{Mode: governanceMode, RetainUntilDate: time.Now().UTC().Format(time.RFC3339)}
|
|
w, r = prepareTestRequest(t, bktName, objName, retention)
|
|
hc.Handler().PutObjectRetentionHandler(w, r)
|
|
assertS3Error(t, w, apiErrors.GetAPIError(apiErrors.ErrInternalError))
|
|
|
|
retention = &data.Retention{Mode: complianceMode, RetainUntilDate: time.Now().UTC().Format(time.RFC3339)}
|
|
w, r = prepareTestRequest(t, bktName, objName, retention)
|
|
r.Header.Set(api.AmzBypassGovernanceRetention, strconv.FormatBool(true))
|
|
hc.Handler().PutObjectRetentionHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objName, nil)
|
|
hc.Handler().GetObjectRetentionHandler(w, r)
|
|
assertRetention(t, w, retention)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objName, retention)
|
|
r.Header.Set(api.AmzBypassGovernanceRetention, strconv.FormatBool(true))
|
|
hc.Handler().PutObjectRetentionHandler(w, r)
|
|
assertS3Error(t, w, apiErrors.GetAPIError(apiErrors.ErrInternalError))
|
|
}
|
|
|
|
func assertRetention(t *testing.T, w *httptest.ResponseRecorder, retention *data.Retention) {
|
|
actualRetention := &data.Retention{}
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualRetention)
|
|
require.NoError(t, err)
|
|
require.Equal(t, retention.Mode, actualRetention.Mode)
|
|
require.Equal(t, retention.RetainUntilDate, actualRetention.RetainUntilDate)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func TestPutObjectWithLock(t *testing.T) {
|
|
ctx := context.Background()
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-lock-enabled"
|
|
lockConfig := &data.ObjectLockConfiguration{
|
|
ObjectLockEnabled: enabledValue,
|
|
Rule: &data.ObjectLockRule{
|
|
DefaultRetention: &data.DefaultRetention{
|
|
Days: 1,
|
|
Mode: governanceMode,
|
|
},
|
|
},
|
|
}
|
|
createTestBucketWithLock(ctx, t, hc, bktName, lockConfig)
|
|
|
|
objDefault := "obj-default-retention"
|
|
|
|
w, r := prepareTestRequest(t, bktName, objDefault, nil)
|
|
hc.Handler().PutObjectHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objDefault, nil)
|
|
hc.Handler().GetObjectRetentionHandler(w, r)
|
|
expectedRetention := &data.Retention{
|
|
Mode: governanceMode,
|
|
RetainUntilDate: time.Now().Add(24 * time.Hour).Format(time.RFC3339),
|
|
}
|
|
assertRetentionApproximate(t, w, expectedRetention, 1)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objDefault, nil)
|
|
hc.Handler().GetObjectLegalHoldHandler(w, r)
|
|
assertLegalHold(t, w, legalHoldOff)
|
|
|
|
objOverride := "obj-override-retention"
|
|
w, r = prepareTestRequest(t, bktName, objOverride, nil)
|
|
r.Header.Set(api.AmzObjectLockMode, complianceMode)
|
|
r.Header.Set(api.AmzObjectLockLegalHold, legalHoldOn)
|
|
r.Header.Set(api.AmzBypassGovernanceRetention, "true")
|
|
r.Header.Set(api.AmzObjectLockRetainUntilDate, time.Now().Add(2*24*time.Hour).Format(time.RFC3339))
|
|
hc.Handler().PutObjectHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objOverride, nil)
|
|
hc.Handler().GetObjectRetentionHandler(w, r)
|
|
expectedRetention = &data.Retention{
|
|
Mode: complianceMode,
|
|
RetainUntilDate: time.Now().Add(2 * 24 * time.Hour).Format(time.RFC3339),
|
|
}
|
|
assertRetentionApproximate(t, w, expectedRetention, 1)
|
|
|
|
w, r = prepareTestRequest(t, bktName, objOverride, nil)
|
|
hc.Handler().GetObjectLegalHoldHandler(w, r)
|
|
assertLegalHold(t, w, legalHoldOn)
|
|
}
|
|
|
|
func assertRetentionApproximate(t *testing.T, w *httptest.ResponseRecorder, retention *data.Retention, delta float64) {
|
|
actualRetention := &data.Retention{}
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualRetention)
|
|
require.NoError(t, err)
|
|
require.Equal(t, retention.Mode, actualRetention.Mode)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
actualUntil, err := time.Parse(time.RFC3339, actualRetention.RetainUntilDate)
|
|
require.NoError(t, err)
|
|
|
|
expectedUntil, err := time.Parse(time.RFC3339, retention.RetainUntilDate)
|
|
require.NoError(t, err)
|
|
|
|
require.InDelta(t, expectedUntil.Unix(), actualUntil.Unix(), delta)
|
|
}
|