diff --git a/tests/commands/head_object.sh b/tests/commands/head_object.sh index ea44da41..4ea9a355 100644 --- a/tests/commands/head_object.sh +++ b/tests/commands/head_object.sh @@ -44,7 +44,7 @@ head_object() { } head_object_rest_expect_success() { - if ! check_param_count_v2 "bucket, object, env vars" 4 $#; then + if ! check_param_count_v2 "bucket, object, env vars" 3 $#; then return 1 fi env_vars="BUCKET_NAME=$1 OBJECT_KEY=$2 $3" diff --git a/tests/drivers/get_bucket_cors/get_bucket_cors_rest.sh b/tests/drivers/get_bucket_cors/get_bucket_cors_rest.sh new file mode 100644 index 00000000..9a639734 --- /dev/null +++ b/tests/drivers/get_bucket_cors/get_bucket_cors_rest.sh @@ -0,0 +1,89 @@ +#!/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. + +source ./tests/drivers/rest.sh + +check_cors_404_content_type_header_and_bucket_name() { + if ! check_param_count_v2 "data file" 1 $#; then + return 1 + fi + if ! check_for_header_key_and_value "$1" "Content-Type" "application/xml"; then + log 2 "error checking Content-Type header and value" + return 1 + fi + if ! check_specific_argument_name_and_value "$1"; then + log 2 "error checking BucketName" + return 1 + fi + return 0 +} + +get_bucket_cors_check_404_header_and_bucket_name() { + if ! check_param_count_v2 "bucket name" 1 $#; then + return 1 + fi + argument_name="BucketName" + argument_value="$1" + if ! send_rest_go_command_expect_error_callback "404" "NoSuchCORSConfiguration" "The CORS configuration does not exist" \ + "check_cors_404_content_type_header_and_bucket_name" "-bucketName" "$1" "-query" "cors"; then + log 2 "error sending get cors command and checking result" + return 1 + fi + return 0 +} + +check_cors_response_data() { + if ! check_param_count_v2 "data file, allowed origin, allowed method one, allowed method two" 4 $#; then + return 1 + fi + local data_file="$1" + local allowed_origin="$2" + local allowed_method_one="$3" + local allowed_method_two="$4" + + if ! check_xml_element "$data_file" "$allowed_origin" "CORSConfiguration" "CORSRule" "AllowedOrigin"; then + log 2 "error checking for allowed origin value of '$allowed_origin'" + return 1 + fi + if ! check_if_element_exists "$data_file" "$allowed_method_one" "CORSConfiguration" "CORSRule" "AllowedMethod"; then + log 2 "error checking if allowed method '$allowed_method_one' exists" + return 1 + fi + if ! check_if_element_exists "$data_file" "$allowed_method_two" "CORSConfiguration" "CORSRule" "AllowedMethod"; then + log 2 "error checking if allowed method '$allowed_method_two' exists" + return 1 + fi + return 0 +} + +get_bucket_cors_check_valid_data() { + if ! check_param_count_v2 "bucket name" 1 $#; then + return 1 + fi + allowed_origin="http://example.com" + allowed_method_one="GET" + allowed_method_two="PUT" + payload="$allowed_origin$allowed_method_one$allowed_method_two" + if ! send_openssl_go_command "200" "-bucketName" "$1" "-query" "cors" "-method" "PUT" "-payload" "$payload" "-contentMD5"; then + log 2 "error sending PutBucketCors go command" + return 1 + fi + if ! send_rest_go_command_callback "200" "check_cors_response_data" "-query" "cors" "-bucketName" "$1" -- "$allowed_origin" "$allowed_method_one" "$allowed_method_two"; then + log 2 "error sending GetCors command or checking response" + return 1 + fi + return 0 +} \ No newline at end of file diff --git a/tests/drivers/head_object/head_object_rest.sh b/tests/drivers/head_object/head_object_rest.sh index e15ba275..34793d27 100644 --- a/tests/drivers/head_object/head_object_rest.sh +++ b/tests/drivers/head_object/head_object_rest.sh @@ -48,7 +48,7 @@ verify_object_exists() { if ! check_param_count_v2 "bucket name, key" 2 $#; then return 1 fi - if ! head_object_rest_expect_success "$1" "$2" "" "200"; then + if ! head_object_rest_expect_success "$1" "$2" ""; then log 2 "error sending HeadObject command and verifying existence" return 1 fi diff --git a/tests/drivers/rest.sh b/tests/drivers/rest.sh index 249696d3..957ecb65 100644 --- a/tests/drivers/rest.sh +++ b/tests/drivers/rest.sh @@ -103,7 +103,7 @@ check_rest_expected_header_error() { status_message=$(echo "$status_line" | cut -d' ' -f3- | tr -d '\r') log 5 "status code: $status_code, status message: $status_message" if [ "$2" != "$status_code" ]; then - log 2 "expected curl response '$2', was '$status_code'" + log 2 "expected curl response '$2', was '$status_code' ($(echo -n "$result"))" return 1 fi if [ "$status_message" != "$3" ]; then @@ -253,11 +253,49 @@ send_rest_go_command() { return 0 } +# return 0 for callback params, 1 for only go params +get_go_params() { + for param in "$@"; do + if [[ "$param" == "--" ]]; then + return 1 + fi + log 5 "param: $param" + echo "$param" + done + return 0 +} + +get_callback_params() { + delimiter_found=false + for param in "$@"; do + if [ "$delimiter_found" == "true" ]; then + echo "$param" + continue + fi + if [ "$param" == "--" ]; then + delimiter_found=true + fi + done + return 0 +} + send_rest_go_command_callback() { if ! check_param_count_gt "response code, callback, params" 2 $#; then return 1 fi - if ! rest_go_command_perform_send "${@:3}"; then + + local all_params=("${@:3}") no_callback_params=0 go_params go_param_array=() callback_params=() + go_params=$(get_go_params "${all_params[@]}") || no_callback_params=$? + while IFS= read -r line; do + go_param_array+=("$line") + done <<< "$go_params" + if [ "$no_callback_params" -eq 1 ]; then + while IFS= read -r line; do + callback_params+=("$line") + done <<< "$(get_callback_params "${all_params[@]}")" + fi + + if ! rest_go_command_perform_send "${go_param_array[@]}"; then log 2 "error sending rest go command" return 1 fi @@ -274,7 +312,7 @@ send_rest_go_command_callback() { return 1 fi echo -n "$result" > "$TEST_FILE_FOLDER/$output_file_name" - if [ "$2" != "" ] && ! "$2" "$TEST_FILE_FOLDER/$output_file_name"; then + if [ "$2" != "" ] && ! "$2" "$TEST_FILE_FOLDER/$output_file_name" "${callback_params[@]}"; then log 2 "error in callback" return 1 fi diff --git a/tests/drivers/xml.sh b/tests/drivers/xml.sh index 380f2bb7..a24cf8a8 100644 --- a/tests/drivers/xml.sh +++ b/tests/drivers/xml.sh @@ -1,5 +1,19 @@ #!/usr/bin/env bash +# 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. + build_xpath_string() { if ! check_param_count_gt "XML tree" 1 $#; then return 1 @@ -14,13 +28,23 @@ build_xpath_string_for_element() { if ! check_param_count_gt "XML tree" 1 $#; then return 1 fi - xpath='//' + local xpath='//' for ((idx=1;idx<=$#;idx++)); do + if [ "${!idx}" == "" ]; then + log 2 "param number $idx is empty" + return 1 + fi + if [[ "${!idx}" =~ [[:space:]] ]]; then + log 2 "param '${!idx}' contains a space" + return 1 + fi xpath+='*[local-name()="'${!idx}'"]' if [ "$idx" != $# ]; then xpath+='/' fi done + echo "$xpath" + return 0 } check_for_empty_element() { @@ -29,8 +53,8 @@ check_for_empty_element() { fi # shellcheck disable=SC2068 - if ! build_xpath_string_for_element ${@:2}; then - log 2 "error building XPath search string" + if ! xpath=$(build_xpath_string_for_element ${@:2} 2>&1); then + log 2 "error building XPath search string: $xpath" return 1 fi if ! get_xml_data "$1" "$1.xml"; then @@ -50,7 +74,7 @@ get_element() { return 1 fi - if ! build_xpath_string_for_element "${@:2}"; then + if ! xpath=$(build_xpath_string_for_element "${@:2}" 2>&1); then log 2 "error building XPath search string" return 1 fi @@ -62,13 +86,12 @@ get_element() { } get_element_text() { - if [ $# -lt 2 ]; then - log 2 "'get_element_text' requires data file, XML tree" + if ! check_param_count_gt "data file, XML tree" 2 $#; then return 1 fi - if ! build_xpath_string_for_element "${@:2}"; then - log 2 "error building XPath search string" + if ! xpath=$(build_xpath_string_for_element "${@:2}" 2>&1); then + log 2 "error building XPath search string: $xpath" return 1 fi @@ -166,8 +189,8 @@ check_if_element_exists() { if ! check_param_count_gt "data file, element, XML tree" 3 $#; then return 1 fi - if ! build_xpath_string_for_element "${@:3}"; then - log 2 "error building XPath search string" + if ! xpath=$(build_xpath_string_for_element "${@:3}" 2>&1); then + log 2 "error building XPath search string: $xpath" return 1 fi @@ -191,6 +214,11 @@ get_xml_data() { if ! check_param_count_v2 "data file, output file" 2 $#; then return 1 fi + + if [ ! -e "$1" ]; then + log 2 "file '$1' does not exist" + return 1 + fi log 5 "data: $(cat "$1")" # Find first line with "&1); then + log 2 "error getting output file file name: $output_file" + return 1 + fi + if ! expected_data=$(get_file_name 2>&1); then + log 2 "error getting expected data file name: $expected_data" + return 1 + fi + if ! get_xml_data "$1" "$TEST_FILE_FOLDER/$output_file"; then + log 2 "error getting xml data" + return 1 + fi + echo -en "$2" > "$TEST_FILE_FOLDER/$expected_data" + if ! diff "$TEST_FILE_FOLDER/$expected_data" "$TEST_FILE_FOLDER/$output_file"; then + return 1 + fi + return 0 +} diff --git a/tests/logger.sh b/tests/logger.sh index 380ae517..df3d93cb 100644 --- a/tests/logger.sh +++ b/tests/logger.sh @@ -22,7 +22,7 @@ check_log_params() { return 1 fi if [ "$3" -ne "$4" ]; then - echo "function $1 requires $2" 2 + echo "function $1 requires $2" >&2 return 1 fi return 0 diff --git a/tests/rest_scripts/command/wholePayload.go b/tests/rest_scripts/command/wholePayload.go index 1657b5e2..185117f1 100644 --- a/tests/rest_scripts/command/wholePayload.go +++ b/tests/rest_scripts/command/wholePayload.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "io" "os" ) @@ -46,6 +47,9 @@ func (w *WholePayload) WritePayload(filePath string) error { var bytesRead int bytesRead, err = sourceFile.Read(buffer) if err != nil { + if err == io.EOF { + break + } return fmt.Errorf("error reading data bytes: %w", err) } if bytesRead == 0 { @@ -55,5 +59,8 @@ func (w *WholePayload) WritePayload(filePath string) error { return fmt.Errorf("error writing bytes to file: %w", err) } } + if err := outFile.Close(); err != nil { + return fmt.Errorf("error closing output file: %w", err) + } return nil } diff --git a/tests/test_rest_cors.sh b/tests/test_rest_cors.sh new file mode 100755 index 00000000..00817c7a --- /dev/null +++ b/tests/test_rest_cors.sh @@ -0,0 +1,147 @@ +#!/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/drivers/create_bucket/create_bucket_rest.sh +source ./tests/drivers/get_bucket_cors/get_bucket_cors_rest.sh +source ./tests/setup.sh + +@test "REST - GetCors - correct content-type, and returns bucket name" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1842" + fi + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name=$output + + run setup_bucket_v2 "$bucket_name" + assert_success + + run get_bucket_cors_check_404_header_and_bucket_name "$bucket_name" + assert_success +} + +@test "REST - PutBucketCors and GetBucketCors - valid configuration" { + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name=$output + + run setup_bucket_v2 "$bucket_name" + assert_success + + run get_bucket_cors_check_valid_data "$bucket_name" + assert_success +} + +@test "REST - CORS - empty CORS rule" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1863" + fi + 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" "MalformedXML" "did not validate" "-bucketName" "$bucket_name" "-query" "cors" "-method" "PUT" \ + "-payload" "" "-contentMD5" + assert_success +} + +@test "REST - CORS - missing allowed origin" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1863" + fi + 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" "MalformedXML" "did not validate" "-bucketName" "$bucket_name" "-query" "cors" "-method" "PUT" \ + "-payload" "GET" "-contentMD5" + assert_success +} + +@test "REST - CORS - missing allowed method" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1863" + fi + 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" "MalformedXML" "did not validate" "-bucketName" "$bucket_name" "-query" "cors" "-method" "PUT" \ + "-payload" "example.com" "-contentMD5" + assert_success +} + +@test "REST - CORS - empty allowed method" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1863" + fi + 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" "InvalidRequest" "unsupported HTTP method in CORS config" "-bucketName" "$bucket_name" "-query" "cors" "-method" "PUT" \ + "-payload" "example.com" "-contentMD5" + assert_success +} + +@test "REST - CORS - invalid origin" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1870" + fi + 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" "InvalidRequest" "can not have more than one wildcard" "-bucketName" "$bucket_name" "-query" "cors" "-method" "PUT" \ + "-payload" "*example*.comGET" "-contentMD5" + assert_success +} + +@test "REST - CORS - delete" { + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name=$output + + run setup_bucket_v2 "$bucket_name" + assert_success + + run get_bucket_cors_check_valid_data "$bucket_name" + assert_success + + run send_rest_go_command "204" "-bucketName" "$bucket_name" "-query" "cors" "-method" "DELETE" + assert_success + + run send_rest_go_command_expect_error "404" "NoSuchCORSConfiguration" "does not exist" "-query" "cors" "-bucketName" "$bucket_name" + assert_success +} diff --git a/tests/test_rest_head_object.sh b/tests/test_rest_head_object.sh index fc37374f..2cbd6d47 100755 --- a/tests/test_rest_head_object.sh +++ b/tests/test_rest_head_object.sh @@ -43,3 +43,25 @@ source ./tests/drivers/get_object_attributes/get_object_attributes_rest.sh run get_etag_attribute_rest "$bucket_name" "$test_file" "$expected_etag" assert_success } + +@test "REST - HeadObject - default Content-Type is binary/octet-stream" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1849" + fi + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run get_file_name + assert_success + test_file="$output" + + run setup_bucket_and_file_v2 "$bucket_name" "$test_file" + assert_success + + run put_object_rest "$TEST_FILE_FOLDER/$test_file" "$bucket_name" "$test_file" + assert_success + + run head_object_check_header_key_and_value "$bucket_name" "$test_file" "Content-Type" "binary/octet-stream" + assert_success +} diff --git a/tests/test_rest_list_buckets.sh b/tests/test_rest_list_buckets.sh index 8ec90d8c..6efe90c2 100755 --- a/tests/test_rest_list_buckets.sh +++ b/tests/test_rest_list_buckets.sh @@ -222,3 +222,19 @@ export RUN_USERS=true run send_rest_go_command_expect_error_with_specific_arg_names_values "405" "MethodNotAllowed" "is not allowed" 4 "Method" "POST" "ResourceType" "SERVICE" "-method" "POST" assert_success } + +@test "REST - ListBuckets - invalid method" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1846" + fi + run send_rest_go_command_expect_error "400" "BadRequest" "An error occurred when parsing the HTTP request" "-method" "GETS" + assert_success +} + +@test "REST - ListBuckets - error Content-Type is application/xml" { + if [ "$DIRECT" != "true" ]; then + skip "https://github.com/versity/versitygw/issues/1852" + fi + run send_rest_go_command_check_header_key_and_value "400" "Content-Type" "application/xml" "-method" "GETS" + 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 ce3a6830..5b32d190 100755 --- a/tests/test_rest_put_object.sh +++ b/tests/test_rest_put_object.sh @@ -25,6 +25,7 @@ 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 +source ./tests/drivers/get_object/get_object_rest.sh test_file="test_file" export RUN_USERS=true @@ -517,3 +518,29 @@ export RUN_USERS=true run rest_check_legal_hold "$bucket_name" "$test_file" assert_success } + +@test "REST - PutObject - openssl go non-file payload" { + run get_bucket_name "$BUCKET_ONE_NAME" + assert_success + bucket_name="$output" + + run get_file_name + assert_success + test_file=$output + + run setup_bucket_v2 "$bucket_name" + assert_success + + run bash -c "tr -dc 'a-zA-Z0-9 ' < /dev/urandom | head -c 100" + assert_success + payload_content=$output + + run bash -c "echo -n \"$payload_content\" > $TEST_FILE_FOLDER/$test_file" + assert_success + + run send_openssl_go_command "200" "-method" "PUT" "-payload" "$payload_content" "-bucketName" "$bucket_name" "-objectKey" "$test_file" + assert_success + + run download_and_compare_file "$TEST_FILE_FOLDER/$test_file" "$bucket_name" "$test_file" "$TEST_FILE_FOLDER/${test_file}_downloaded" + assert_success +} diff --git a/tests/test_xml.sh b/tests/test_xml.sh new file mode 100755 index 00000000..0e363ee0 --- /dev/null +++ b/tests/test_xml.sh @@ -0,0 +1,87 @@ +#!/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/drivers/file.sh +source ./tests/drivers/xml.sh +source ./tests/drivers/params.sh +source ./tests/logger.sh +source ./tests/setup_unit.sh + +@test "build_xpath_string_for_element" { + run build_xpath_string_for_element + assert_failure + assert_output -p "requires" + + run build_xpath_string_for_element "Error" + assert_success + assert_output '//*[local-name()="Error"]' + + run build_xpath_string_for_element "Error" "Code" + assert_success + assert_output '//*[local-name()="Error"]/*[local-name()="Code"]' + + run build_xpath_string_for_element "dontcare" "" + assert_failure + assert_output -p 'param number 2 is empty' + + run build_xpath_string_for_element "XML With Space" + assert_failure + assert_output -p "param 'XML With Space' contains a space" +} + +@test "get_xml_data - missing params" { + run get_xml_data "oneparam" + assert_failure + assert_output -p "requires data file, output file" +} + +@test "get_xml_data - file doesn't exist" { + run get_xml_data "/nonexistent_$$" "/tmp/out" + assert_failure + assert_output -p "does not exist" +} + +@test "get_xml_data - no XML content" { + input=$(get_file_name) + printf 'HTTP/1.1 500\r\n\r\nNo XML here' > "$TEST_FILE_FOLDER/$input" + run get_xml_data "$TEST_FILE_FOLDER/$input" "dontcare" + assert_failure + assert_output -p "No XML declaration found" +} + +@test "get_xml_data - valid XML with declaration" { + input=$(get_file_name) + printf 'HTTP/1.1 200\r\n\r\nOK' > "$TEST_FILE_FOLDER/$input" + run compare_data_with_xml_file "$TEST_FILE_FOLDER/$input" "\nOK" + assert_success +} + +@test "get_xml_data - valid XML without declaration" { + input=$(get_file_name) + printf 'HTTP/1.1 200\r\n\r\nAlsoOK' > "$TEST_FILE_FOLDER/$input" + run compare_data_with_xml_file "$TEST_FILE_FOLDER/$input" "\nAlsoOK" + assert_success +} + +@test "get_xml_data - XML with extra content after root" { + input=$(get_file_name) + printf 'HTTP/1.1 200\r\n\r\nAgainOKextra' > "$TEST_FILE_FOLDER/$input" + run compare_data_with_xml_file "$TEST_FILE_FOLDER/$input" "\nAgainOK" + assert_success +}