From 2a23686c87d8eb4cd4146c3ac8c6614ced5fad34 Mon Sep 17 00:00:00 2001 From: Luke McCrone Date: Tue, 6 Jan 2026 11:30:11 -0300 Subject: [PATCH] test: PutObject, locking, versioning, retention testing --- tests/commands/copy_object.sh | 2 +- tests/commands/create_bucket.sh | 4 +- tests/commands/delete_object.sh | 11 ++ tests/commands/get_bucket_policy.sh | 2 +- tests/commands/put_bucket_policy.sh | 2 +- tests/commands/put_object.sh | 2 +- .../commands/put_object_lock_configuration.sh | 22 ++++ .../delete_object/delete_object_rest.sh | 48 ++++++++ .../list_object_versions_rest.sh | 112 ++++++++++++++++-- tests/drivers/rest.sh | 28 +++++ tests/generate_matrix.sh | 2 +- .../put_object_lock_configuration.sh | 22 ++-- tests/test_rest_list_object_versions.sh | 34 ++++++ tests/test_rest_put_bucket_versioning.sh | 34 ++++++ tests/test_rest_put_object.sh | 101 ++++++++++++++++ tests/test_rest_put_object_lock_config.sh | 73 ++++++++++++ tests/util/util_versioning.sh | 59 ++++----- 17 files changed, 501 insertions(+), 57 deletions(-) create mode 100755 tests/test_rest_put_bucket_versioning.sh create mode 100755 tests/test_rest_put_object_lock_config.sh diff --git a/tests/commands/copy_object.sh b/tests/commands/copy_object.sh index 3a0730f8..614714c5 100644 --- a/tests/commands/copy_object.sh +++ b/tests/commands/copy_object.sh @@ -28,7 +28,7 @@ copy_object() { error=$(send_command aws --no-verify-ssl s3api copy-object --copy-source "$2" --bucket "$3" --key "$4" 2>&1) || exit_code=$? elif [[ $1 == 's3cmd' ]]; then log 5 "s3cmd ${S3CMD_OPTS[*]} --no-check-certificate cp s3://$2 s3://$3/$4" - error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate cp "s3://$2" s3://"$3/$4" 2>&1) || exit_code=$? + error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate --region "$AWS_REGION" cp "s3://$2" s3://"$3/$4" 2>&1) || exit_code=$? elif [[ $1 == 'mc' ]]; then error=$(send_command mc --insecure cp "$MC_ALIAS/$2" "$MC_ALIAS/$3/$4" 2>&1) || exit_code=$? else diff --git a/tests/commands/create_bucket.sh b/tests/commands/create_bucket.sh index a8937ea9..9f20fa2b 100644 --- a/tests/commands/create_bucket.sh +++ b/tests/commands/create_bucket.sh @@ -59,7 +59,7 @@ create_bucket_invalid_name() { elif [[ $1 == 's3api' ]]; then bucket_create_error=$(aws --no-verify-ssl s3api create-bucket --bucket "s3://" 2>&1) || exit_code=$? elif [[ $1 == 's3cmd' ]]; then - bucket_create_error=$(s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate mb "s3://" 2>&1) || exit_code=$? + bucket_create_error=$(s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate mb --region="$AWS_REGION" "s3://" 2>&1) || exit_code=$? elif [[ $1 == 'mc' ]]; then bucket_create_error=$(mc --insecure mb "$MC_ALIAS/." 2>&1) || exit_code=$? else @@ -82,7 +82,7 @@ create_bucket_with_user() { if [[ $1 == "aws" ]] || [[ $1 == "s3api" ]]; then error=$(AWS_ACCESS_KEY_ID="$3" AWS_SECRET_ACCESS_KEY="$4" send_command aws --no-verify-ssl s3 mb s3://"$2" 2>&1) || exit_code=$? elif [[ $1 == "s3cmd" ]]; then - error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate mb --access_key="$3" --secret_key="$4" s3://"$2" 2>&1) || exit_code=$? + error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate mb --access_key="$3" --secret_key="$4" --region="$AWS_REGION" s3://"$2" 2>&1) || exit_code=$? elif [[ $1 == "mc" ]]; then error=$(send_command mc --insecure mb "$MC_ALIAS"/"$2" 2>&1) || exit_code=$? else diff --git a/tests/commands/delete_object.sh b/tests/commands/delete_object.sh index f4cd06b2..6f7d7da2 100644 --- a/tests/commands/delete_object.sh +++ b/tests/commands/delete_object.sh @@ -85,6 +85,17 @@ delete_object_version_rest() { return 0 } +delete_object_version_rest_expect_error() { + if ! check_param_count_v2 "bucket name, object name, version ID, expected code, expected error, expected message" 6 $#; then + return 1 + fi + if ! send_rest_command_expect_error "BUCKET_NAME=$1 OBJECT_KEY=$2 VERSION_ID=$3" "./tests/rest_scripts/delete_object.sh" "$4" "$5" "$6"; then + log 2 "error deleting object: $result" + return 1 + fi + return 0 +} + delete_object_version_bypass_retention() { if ! check_param_count "delete_object_version_bypass_retention" "bucket, key, version ID" 3 $#; then return 1 diff --git a/tests/commands/get_bucket_policy.sh b/tests/commands/get_bucket_policy.sh index b9c9425a..301e6a8b 100644 --- a/tests/commands/get_bucket_policy.sh +++ b/tests/commands/get_bucket_policy.sh @@ -87,7 +87,7 @@ get_bucket_policy_s3cmd() { return 1 fi - if ! info=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate info "s3://$1" 2>&1); then + if ! info=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate --region "$AWS_REGION" info "s3://$1" 2>&1); then log 2 "error getting bucket policy: $info" return 1 fi diff --git a/tests/commands/put_bucket_policy.sh b/tests/commands/put_bucket_policy.sh index dedf7266..9b60691a 100644 --- a/tests/commands/put_bucket_policy.sh +++ b/tests/commands/put_bucket_policy.sh @@ -26,7 +26,7 @@ put_bucket_policy() { if [[ $1 == 's3api' ]]; then policy=$(send_command aws --no-verify-ssl s3api put-bucket-policy --bucket "$2" --policy "file://$3" 2>&1) || put_policy_result=$? elif [[ $1 == 's3cmd' ]]; then - policy=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate setpolicy "$3" "s3://$2" 2>&1) || put_policy_result=$? + policy=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate --region "$AWS_REGION" setpolicy "$3" "s3://$2" 2>&1) || put_policy_result=$? elif [[ $1 == 'mc' ]]; then policy=$(send_command mc --insecure anonymous set-json "$3" "$MC_ALIAS/$2" 2>&1) || put_policy_result=$? elif [ "$1" == 'rest' ]; then diff --git a/tests/commands/put_object.sh b/tests/commands/put_object.sh index 24d9017c..95519b34 100644 --- a/tests/commands/put_object.sh +++ b/tests/commands/put_object.sh @@ -30,7 +30,7 @@ put_object() { elif [[ $1 == 's3api' ]]; then error=$(send_command aws --no-verify-ssl s3api put-object --body "$2" --bucket "$3" --key "$4" 2>&1) || exit_code=$? elif [[ $1 == 's3cmd' ]]; then - error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate put "$2" s3://"$3/$4" 2>&1) || exit_code=$? + error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate --region "$AWS_REGION" put "$2" s3://"$3/$4" 2>&1) || exit_code=$? elif [[ $1 == 'mc' ]]; then error=$(send_command mc --insecure put "$2" "$MC_ALIAS/$3/$4" 2>&1) || exit_code=$? elif [[ $1 == 'rest' ]]; then diff --git a/tests/commands/put_object_lock_configuration.sh b/tests/commands/put_object_lock_configuration.sh index ad373d61..5fd02d2f 100644 --- a/tests/commands/put_object_lock_configuration.sh +++ b/tests/commands/put_object_lock_configuration.sh @@ -27,6 +27,28 @@ put_object_lock_configuration() { return 0 } +put_object_lock_configuration_rest() { + if ! check_param_count_v2 "bucket name, params" 2 $#; then + return 1 + fi + if ! send_rest_command "BUCKET_NAME=$1 $2" "./tests/rest_scripts/put_object_lock_configuration.sh"; then + log 2 "error sending put object lock config command or error mismatch" + return 1 + fi + return 0 +} + +put_object_lock_configuration_rest_expect_error() { + if ! check_param_count_v2 "bucket name, params, expected response code, expected error code, expected message" 5 $#; then + return 1 + fi + if ! send_rest_command_expect_error "BUCKET_NAME=$1 $2" "./tests/rest_scripts/put_object_lock_configuration.sh" "$3" "$4" "$5"; then + log 2 "error sending put object lock config command or error mismatch" + return 1 + fi + return 0 +} + remove_retention_policy_rest() { if ! check_param_count "remove_retention_policy_rest" "bucket" 1 $#; then return 1 diff --git a/tests/drivers/delete_object/delete_object_rest.sh b/tests/drivers/delete_object/delete_object_rest.sh index f7d59a46..953c9096 100644 --- a/tests/drivers/delete_object/delete_object_rest.sh +++ b/tests/drivers/delete_object/delete_object_rest.sh @@ -15,6 +15,7 @@ # under the License. source ./tests/commands/list_objects_v2.sh +source ./tests/drivers/list_object_versions/list_object_versions_rest.sh source ./tests/drivers/xml.sh source ./tests/util/util_legal_hold.sh @@ -102,3 +103,50 @@ delete_object_version_with_or_without_retention_base64() { log 5 "successfully deleted version with key '$key', id '$id'" return 0 } + +put_object_with_lock_mode_and_delete_latest_version() { + if ! check_param_count_v2 "file, bucket, key, later time" 4 $#; then + return 1 + fi + if ! send_rest_go_command "200" \ + "-bucketName" "$2" "-objectKey" "$3" "-payloadFile" "$1" \ + "-method" "PUT" "-contentMD5" "-signedParams" "x-amz-object-lock-mode:GOVERNANCE,x-amz-object-lock-retain-until-date:$4"; then + log 2 "error sending put object command with object lock" + return 1 + fi + if ! send_rest_go_command_callback "200" "parse_latest_version_id" \ + "-method" "GET" "-bucketName" "$2" "-query" "versions="; then + log 2 "error checking versions before deletion" + return 1 + fi + if ! delete_object_version_rest_expect_error "$2" "$3" "$version_id" "403" "AccessDenied" "object protected by object lock"; then + log 2 "shouldn't have been able to delete" + return 1 + fi + sleep 15 + if ! delete_object_version "$2" "$3" "$version_id"; then + log 2 "error deleting object version" + return 1 + fi + return 0 +} + +attempt_to_delete_version_after_retention_policy() { + if ! check_param_count_v2 "file, bucket name, key" 3 $#; then + return 1 + fi + if ! send_rest_go_command "200" \ + "-bucketName" "$2" "-objectKey" "$3" "-payloadFile" "$1" "-method" "PUT" "-contentMD5"; then + log 2 "error sending put object command" + return 1 + fi + if ! send_rest_go_command_callback "200" "parse_latest_version_id" \ + "-method" "GET" "-bucketName" "$2" "-query" "versions="; then + log 2 "error checking versions before deletion" + return 1 + fi + if ! delete_object_version_rest_expect_error "$2" "$3" "$version_id" "403" "AccessDenied" "object protected by object lock"; then + log 2 "shouldn't have been able to delete" + return 1 + fi +} diff --git a/tests/drivers/list_object_versions/list_object_versions_rest.sh b/tests/drivers/list_object_versions/list_object_versions_rest.sh index b819a1ab..e393142d 100644 --- a/tests/drivers/list_object_versions/list_object_versions_rest.sh +++ b/tests/drivers/list_object_versions/list_object_versions_rest.sh @@ -14,23 +14,23 @@ # specific language governing permissions and limitations # under the License. -parse_non_latest_version_id() { - if ! check_param_count_v2 "data file" 1 $#; then +parse_version_id() { + if ! check_param_count_v2 "data file, IsLatest val" 2 $#; then return 1 fi log 5 "data: $(cat "$1")" - not_latest_string="//*[local-name()=\"Version\"][*[local-name()=\"IsLatest\" and text()=\"false\"]]" - log 5 "match string: $not_latest_string" + version_string="//*[local-name()=\"Version\"][*[local-name()=\"IsLatest\" and text()=\"$2\"]]" + log 5 "match string: $version_string" if ! get_xml_data "$1" "$1.xml"; then log 2 "error getting XML data" return 1 fi - if ! not_latest=$(xmllint --xpath "$not_latest_string" "$1.xml" 2>&1); then - log 2 "error getting result: $not_latest" + if ! version=$(xmllint --xpath "$version_string" "$1.xml" 2>&1); then + log 2 "error getting result: $version" return 1 fi - log 5 "not latest: $not_latest" - if ! version_id=$(xmllint --xpath "//*[local-name()=\"VersionId\"]/text()" <(echo "$not_latest" | head -n 1) 2>&1); then + log 5 "latest: $2, version: $version" + if ! version_id=$(xmllint --xpath "//*[local-name()=\"VersionId\"]/text()" <(echo "$version" | head -n 1) 2>&1); then log 2 "error getting version ID: $version_id" return 1 fi @@ -38,6 +38,17 @@ parse_non_latest_version_id() { return 0 } +parse_non_latest_version_id() { + if ! check_param_count_v2 "data file" 1 $#; then + return 1 + fi + if ! parse_version_id "$1" "false"; then + log 2 "error getting non-latest version ID" + return 1 + fi + return 0 +} + get_non_latest_version() { if ! check_param_count_v2 "bucket" $# 1; then return 1 @@ -48,3 +59,88 @@ get_non_latest_version() { fi return 0 } + +check_object_versions_before_deletion() { + if ! check_param_count_v2 "data file" 1 $#; then + return 1 + fi + if ! get_xml_data "$1" "$1.tmp"; then + log 2 "error getting XML data" + return 1 + fi + if ! parse_versions_rest "$1.tmp"; then + log 2 "error parsing versions" + return 1 + fi + if [ "${#version_ids[@]}" -ne 1 ]; then + log 2 "expected version ID count of 1, was '${#version_ids[@]}'" + return 1 + fi + version_id="${version_ids[0]}" + log 5 "version ID: $version_id" + return 0 +} + +check_object_versions_after_deletion() { + if ! check_param_count_v2 "data file" 1 $#; then + return 1 + fi + if ! get_xml_data "$1" "$1.tmp"; then + log 2 "error getting XML data" + return 1 + fi + if ! parse_versions_rest "$1.tmp"; then + log 2 "error parsing versions" + return 1 + fi + if [ "${#version_ids[@]}" -ne 2 ]; then + log 2 "expected version ID count of 2, was '${#version_ids[@]}'" + return 1 + fi + if [ "${version_ids[0]}" != "$version_id" ]; then + log 2 "expected version ID of '$version_id', was '${version_ids[0]}'" + return 1 + fi + if [ "${version_islatests[0]}" != "false" ]; then + log 2 "expected 'IsLatest' of version ID to be false, was '${version_islatests[0]}'" + return 1 + fi + if [ "${version_islatests[1]}" != "true" ]; then + log 2 "expected 'IsLatest' of delete marker to be true, was '${version_islatests[1]}'" + return 1 + fi + return 0 +} + +list_object_versions_before_and_after_retention_deletion() { + if ! check_param_count_v2 "bucket name, file" 2 $#; then + return 1 + fi + if ! send_rest_go_command_callback "200" "check_object_versions_before_deletion" \ + "-method" "GET" "-bucketName" "$1" "-query" "versions="; then + log 2 "error checking versions before deletion" + return 1 + fi + if ! delete_object_rest "$1" "$2"; then + log 2 "error deleting file" + return 1 + fi + if ! send_rest_go_command_callback "200" "check_object_versions_after_deletion" \ + "-method" "GET" "-bucketName" "$1" "-query" "versions="; then + log 2 "error checking versions before deletion" + return 1 + fi + return 0 +} + +parse_latest_version_id() { + if ! check_param_count_v2 "data file" 1 $#; then + return 1 + fi + if ! parse_version_id "$1" "true"; then + log 2 "error getting latest version ID" + return 1 + fi + log 5 "version ID: $version_id" + return 0 +} diff --git a/tests/drivers/rest.sh b/tests/drivers/rest.sh index 0da56989..228e0802 100644 --- a/tests/drivers/rest.sh +++ b/tests/drivers/rest.sh @@ -289,3 +289,31 @@ check_for_header_key_and_value() { log 2 "no header key '$2' found" return 1 } + +check_argument_name_and_value() { + if ! check_param_count_v2 "data file" 1 $#; then + return 1 + fi + if ! check_error_parameter "$1" "ArgumentName" "$argument_name"; then + log 2 "error checking 'ArgumentName' parameter" + return 1 + fi + if ! check_error_parameter "$1" "ArgumentValue" "$argument_value"; then + log 2 "error checking 'ArgumentValue' parameter" + return 1 + fi + return 0 +} + +send_rest_go_command_expect_error_with_arg_name_value() { + if ! check_param_count_gt "response code, error code, message, arg name, arg value, params" 5 $#; then + return 1 + fi + argument_name=$4 + argument_value=$5 + if ! send_rest_go_command_expect_error_callback "$1" "$2" "$3" "check_argument_name_and_value" "${@:6}"; then + log 2 "error checking error response values" + return 1 + fi + return 0 +} diff --git a/tests/generate_matrix.sh b/tests/generate_matrix.sh index 72ee9ab4..5e7b87f5 100755 --- a/tests/generate_matrix.sh +++ b/tests/generate_matrix.sh @@ -36,7 +36,7 @@ check_for_and_load_test_file_and_params() { iam="folder" fi iam_types+=("$iam") - if [ $(( idx % 4 )) -eq 0 ]; then + if [ $(( idx % 10 )) -eq 0 ]; then region="us-west-1" else region="us-east-1" diff --git a/tests/rest_scripts/put_object_lock_configuration.sh b/tests/rest_scripts/put_object_lock_configuration.sh index 3b900853..5359e6ea 100755 --- a/tests/rest_scripts/put_object_lock_configuration.sh +++ b/tests/rest_scripts/put_object_lock_configuration.sh @@ -34,17 +34,23 @@ omit_content_md5="${OMIT_CONTENT_MD5:=false}" payload=" - Enabled" + Enabled" if [ "$retention_rule" != "false" ]; then - payload+=" - - $retention_days - $retention_mode - $retention_years - + payload+=" + + " + if [ "$retention_days" != "" ]; then + payload+="$retention_days" + fi + payload+="$retention_mode" + if [ "$retention_years" != "" ]; then + payload+="$retention_years" + fi + payload+=" " fi - payload+="" + payload+=" +" payload_hash="$(echo -n "$payload" | sha256sum | awk '{print $1}')" if [ "$omit_content_md5" == "false" ]; then diff --git a/tests/test_rest_list_object_versions.sh b/tests/test_rest_list_object_versions.sh index 1ba1d3a7..9304741f 100755 --- a/tests/test_rest_list_object_versions.sh +++ b/tests/test_rest_list_object_versions.sh @@ -19,6 +19,8 @@ load ./bats-assert/load source ./tests/setup.sh source ./tests/drivers/create_bucket/create_bucket_rest.sh +source ./tests/drivers/list_object_versions/list_object_versions_rest.sh +source ./tests/util/util_time.sh @test "ListObjectVersions - accidental query of versions on object returns correct error" { if [ "$DIRECT" != "true" ]; then @@ -36,4 +38,36 @@ source ./tests/drivers/create_bucket/create_bucket_rest.sh run send_rest_go_command_expect_error "400" "InvalidRequest" "There is no such thing as the ?versions sub-resource for a key" \ "-bucketName" "$bucket_name" "-objectKey" "$test_file" "-query" "versions=" assert_success +} + +@test "ListObjectVersions - version changes after deletion w/retention policy" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1741" + fi + test_file="test_file" + + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_and_file_v2 "$bucket_name" "$test_file" + assert_success + + run put_bucket_versioning_rest "$bucket_name" "Enabled" + assert_success + + run put_object_lock_configuration_rest "$bucket_name" "" + assert_success + + run get_time_seconds_in_future 30 + assert_success + later_date=${output}Z + + run send_rest_go_command "200" \ + "-bucketName" "$bucket_name" "-objectKey" "$test_file" "-payloadFile" "$TEST_FILE_FOLDER/$test_file" \ + "-method" "PUT" "-contentMD5" "-signedParams" "x-amz-object-lock-mode:GOVERNANCE,x-amz-object-lock-retain-until-date:$later_date" + assert_success + + run list_object_versions_before_and_after_retention_deletion "$bucket_name" "$test_file" + assert_success } \ No newline at end of file diff --git a/tests/test_rest_put_bucket_versioning.sh b/tests/test_rest_put_bucket_versioning.sh new file mode 100755 index 00000000..ae6b0aba --- /dev/null +++ b/tests/test_rest_put_bucket_versioning.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bats + +# Copyright 2026 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. + +load ./bats-support/load +load ./bats-assert/load + +source ./tests/setup.sh +source ./tests/drivers/create_bucket/create_bucket_rest.sh + +@test "REST - PutBucketVersioning - empty payload" { + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_v2 "$bucket_name" + assert_success + + run send_rest_go_command_expect_error "400" "MissingRequestBodyError" "Request Body is empty" \ + "-bucketName" "$bucket_name" "-method" "PUT" "-query" "versioning=" + assert_success +} \ No newline at end of file diff --git a/tests/test_rest_put_object.sh b/tests/test_rest_put_object.sh index f0c29e82..253e4875 100755 --- a/tests/test_rest_put_object.sh +++ b/tests/test_rest_put_object.sh @@ -21,8 +21,10 @@ source ./tests/setup.sh source ./tests/drivers/file.sh source ./tests/drivers/create_bucket/create_bucket_rest.sh source ./tests/drivers/head_object/head_object_rest.sh +source ./tests/drivers/list_object_versions/list_object_versions_rest.sh source ./tests/drivers/put_object/put_object_rest.sh source ./tests/util/util_public_access_block.sh +source ./tests/util/util_time.sh test_file="test_file" export RUN_USERS=true @@ -462,3 +464,102 @@ export RUN_USERS=true run head_object_check_header_key_and_value "$bucket_name" "$test_file" "Content-Disposition" "dummy" assert_success } + +@test "REST - PutObject - x-amz-object-lock-retain-until-date - invalid format" { + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_and_file_v2 "$bucket_name" "$test_file" + assert_success + + run send_rest_go_command_expect_error "400" "InvalidArgument" "must be provided in ISO 8601 format" "-bucketName" "$bucket_name" \ + "-objectKey" "$test_file" "-payloadFile" "$TEST_FILE_FOLDER/$test_file" \ + "-method" "PUT" "-contentMD5" "-signedParams" "x-amz-object-lock-mode:abc,x-amz-object-lock-retain-until-date:abc" + assert_success +} + +@test "REST - PutObject - x-amz-object-lock-retain-until-date - earlier date" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1734" + fi + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_and_file_v2 "$bucket_name" "$test_file" + assert_success + + earlier_date="2025-12-25T12:00:00Z" + run send_rest_go_command_expect_error_with_arg_name_value "400" "InvalidArgument" "must be in the future" \ + "x-amz-object-lock-retain-until-date" "$earlier_date" "-bucketName" "$bucket_name" "-objectKey" "$test_file" "-payloadFile" "$TEST_FILE_FOLDER/$test_file" \ + "-method" "PUT" "-contentMD5" "-signedParams" "x-amz-object-lock-mode:abc,x-amz-object-lock-retain-until-date:$earlier_date" + assert_success +} + +@test "REST - PutObject - x-amz-object-lock-mode - invalid mode" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1736" + fi + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_and_file_v2 "$bucket_name" "$test_file" + assert_success + + run get_time_seconds_in_future 10 + assert_success + later_date=${output}Z + + lock_mode="abc" + run send_rest_go_command_expect_error_with_arg_name_value "400" "InvalidArgument" "Unknown wormMode directive" \ + "x-amz-object-lock-mode" "$lock_mode" "-bucketName" "$bucket_name" "-objectKey" "$test_file" "-payloadFile" "$TEST_FILE_FOLDER/$test_file" \ + "-method" "PUT" "-contentMD5" "-signedParams" "x-amz-object-lock-mode:$lock_mode,x-amz-object-lock-retain-until-date:$later_date" + assert_success +} + +@test "TEST - REST - PutObject - not allowed without content-MD5 with lock configuration" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1740" + fi + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_and_file_v2 "$bucket_name" "$test_file" + assert_success + + run put_bucket_versioning_rest "$bucket_name" "Enabled" + assert_success + + run put_object_lock_configuration_rest "$bucket_name" "RETENTION_MODE=GOVERNANCE RETENTION_RULE=true RETENTION_DAYS=1" + assert_success + + run send_rest_go_command_expect_error "400" "InvalidRequest" "is required for Put Object requests with Object Lock parameters" \ + "-bucketName" "$bucket_name" "-objectKey" "$test_file" "-payloadFile" "$TEST_FILE_FOLDER/$test_file" \ + "-method" "PUT" + assert_success +} + +@test "REST - PutObject - object lock - success" { + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_and_file_v2 "$bucket_name" "$test_file" + assert_success + + run put_bucket_versioning_rest "$bucket_name" "Enabled" + assert_success + + run put_object_lock_configuration_rest "$bucket_name" "" + assert_success + + run get_time_seconds_in_future 15 + assert_success + later_date=${output}Z + + run put_object_with_lock_mode_and_delete_latest_version "$TEST_FILE_FOLDER/$test_file" "$bucket_name" "$test_file" "$later_date" + assert_success +} diff --git a/tests/test_rest_put_object_lock_config.sh b/tests/test_rest_put_object_lock_config.sh new file mode 100755 index 00000000..22babf39 --- /dev/null +++ b/tests/test_rest_put_object_lock_config.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bats + +# Copyright 2026 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. + +load ./bats-support/load +load ./bats-assert/load + +source ./tests/setup.sh +source ./tests/drivers/create_bucket/create_bucket_rest.sh + +@test "REST - PutObjectLockConfig - missing payload" { + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_v2 "$bucket_name" + assert_success + + run put_bucket_versioning_rest "$bucket_name" "Enabled" + assert_success + + run send_rest_go_command_expect_error "400" "MissingRequestBodyError" "Request Body is empty" \ + "-bucketName" "$bucket_name" "-method" "PUT" "-query" "object-lock=" "-contentMD5" + assert_success +} + +@test "REST - PutObjectLockConfig - zero day config, correct error code" { + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_v2 "$bucket_name" + assert_success + + run put_bucket_versioning_rest "$bucket_name" "Enabled" + assert_success + + run put_object_lock_configuration_rest_expect_error "$bucket_name" "RETENTION_MODE=GOVERNANCE RETENTION_RULE=true \ + RETENTION_DAYS=0" "400" "InvalidArgument" "Default retention period must be a positive integer value" + assert_success +} + +@test "REST - PutObjectLockConfig - default retention period works" { + test_file="test_file" + + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run setup_bucket_and_file_v2 "$bucket_name" "$test_file" + assert_success + + run put_bucket_versioning_rest "$bucket_name" "Enabled" + assert_success + + run put_object_lock_configuration_rest "$bucket_name" "RETENTION_MODE=GOVERNANCE RETENTION_RULE=true RETENTION_DAYS=1" + assert_success + + run attempt_to_delete_version_after_retention_policy "$TEST_FILE_FOLDER/$test_file" "$bucket_name" "$test_file" + assert_success +} diff --git a/tests/util/util_versioning.sh b/tests/util/util_versioning.sh index 81660d02..421b1d39 100644 --- a/tests/util/util_versioning.sh +++ b/tests/util/util_versioning.sh @@ -23,28 +23,6 @@ check_if_versioning_enabled() { return 1 } -parse_version_data_by_type() { - if ! check_param_count "parse_version_data_by_type" "client, data" 2 $#; then - return 1 - fi - if [ "$1" == "rest" ]; then - log 5 "version data: $versions" - if ! parse_versions_rest "$versions"; then - log 2 "error parsing REST object versions" - return 1 - fi - else - if ! parse_version_data "$versions" '.Versions[]'; then - log 2 "error parsing Versions elements" - return 1 - fi - if ! parse_version_data "$versions" '.DeleteMarkers[]'; then - log 2 "error getting DeleteMarkers elements" - return 1 - fi - fi -} - parse_version_data() { if ! check_param_count "parse_version_data" "raw data, element name" 2 $#; then return 1 @@ -102,10 +80,10 @@ check_versioning_status_rest() { } echo_versions() { - if ! check_param_count_v2 "'Version' or 'DeleteMarker', 'Key' or 'VersionId', file" 3 $#; then + if ! check_param_count_v2 "'Version' or 'DeleteMarker', 'Key' or 'VersionId' or 'IsLatest', file" 3 $#; then return 1 fi - if ! keys=$(echo -n "$versions" | xmllint --xpath "//*[local-name()=\"$1\"]/*[local-name()=\"$2\"]/text()" - | xmlstarlet unesc 2>&1); then + if ! keys=$(xmllint --xpath "//*[local-name()=\"$1\"]/*[local-name()=\"$2\"]/text()" "$3" | xmlstarlet unesc 2>&1); then if [[ "$keys" == *"XPath set is empty"* ]]; then return 0 fi @@ -113,12 +91,6 @@ echo_versions() { return 1 fi log 5 "keys to append: ${keys[*]}" - if ! result=$(truncate -s 0 "$3" 2>&1); then - log 2 "error truncating file: $result" - fi - for key in "${keys[@]}"; do - echo "$key" >> "$3" - done echo "${keys[*]}" } @@ -151,31 +123,50 @@ parse_base64_versions_rest() { } parse_versions_rest() { + if ! check_param_count_v2 "data file" 1 $#; then + return 1 + fi + log 5 "versions file: $(cat "$1")" base64_pairs=() - if ! keys=$(echo_versions "Version" "Key" "$TEST_FILE_FOLDER/version_keys.txt"); then + version_keys=() + version_ids=() + version_islatests=() + if ! keys=$(echo_versions "Version" "Key" "$1"); then log 2 "error getting Version Key values: $keys" return 1 fi # shellcheck disable=SC2206 version_keys+=($keys) - if ! ids=$(echo_versions "Version" "VersionId" "$TEST_FILE_FOLDER/version_ids.txt"); then + if ! ids=$(echo_versions "Version" "VersionId" "$1"); then log 2 "error getting Version VersionId values: $ids" return 1 fi # shellcheck disable=SC2206 version_ids+=($ids) - if ! keys=$(echo_versions "DeleteMarker" "Key" "$TEST_FILE_FOLDER/delete_marker_keys.txt"); then + if ! is_latest=$(echo_versions "Version" "IsLatest" "$1"); then + log 2 "error getting Version IsLatest values: $is_latest" + return 1 + fi + # shellcheck disable=SC2206 + version_islatests+=($is_latest) + if ! keys=$(echo_versions "DeleteMarker" "Key" "$1"); then log 2 "error getting DeleteMarker Key values: $keys" return 1 fi # shellcheck disable=SC2206 version_keys+=($keys) - if ! ids=$(echo_versions "DeleteMarker" "VersionId" "$TEST_FILE_FOLDER/delete_marker_ids.txt"); then + if ! ids=$(echo_versions "DeleteMarker" "VersionId" "$1"); then log 2 "error getting DeleteMarker VersionId values: $ids" return 1 fi # shellcheck disable=SC2206 version_ids+=($ids) + if ! is_latest=$(echo_versions "DeleteMarker" "IsLatest" "$1"); then + log 2 "error getting DeleteMarker IsLatest values: $is_latest" + return 1 + fi + # shellcheck disable=SC2206 + version_islatests+=($is_latest) log 5 "version keys: ${version_keys[*]}" log 5 "version IDs: ${version_ids[*]}" log 5 "base64 pairs: ${base64_pairs[*]}"