Merge pull request #1862 from versity/test/rest_cors

test: REST CORS commands - initial checks
This commit is contained in:
Ben McClelland
2026-02-23 11:52:47 -08:00
committed by GitHub
12 changed files with 500 additions and 16 deletions
+1 -1
View File
@@ -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"
@@ -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="<?xml version=\"1.0\" encoding=\"UTF-8\"?><CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><CORSRule><AllowedOrigin>$allowed_origin</AllowedOrigin><AllowedMethod>$allowed_method_one</AllowedMethod><AllowedMethod>$allowed_method_two</AllowedMethod></CORSRule></CORSConfiguration>"
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
}
@@ -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
+41 -3
View File
@@ -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
+61 -10
View File
@@ -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 "<?xml" and everything from there onward
@@ -236,3 +264,26 @@ check_error_parameter() {
fi
return 0
}
compare_data_with_xml_file() {
if ! check_param_count_v2 "input file, expected data string" 2 $#; then
return 1
fi
if ! output_file=$(get_file_name 2>&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
}
+1 -1
View File
@@ -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
@@ -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
}
+147
View File
@@ -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" "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><CORSRule></CORSRule></CORSConfiguration>" "-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" "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><CORSRule><AllowedMethod>GET</AllowedMethod></CORSRule></CORSConfiguration>" "-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" "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><CORSRule><AllowedOrigin>example.com</AllowedOrigin></CORSRule></CORSConfiguration>" "-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" "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><CORSRule><AllowedOrigin>example.com</AllowedOrigin><AllowedMethod></AllowedMethod></CORSRule></CORSConfiguration>" "-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" "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><CORSRule><AllowedOrigin>*example*.com</AllowedOrigin><AllowedMethod>GET</AllowedMethod></CORSRule></CORSConfiguration>" "-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
}
+22
View File
@@ -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
}
+16
View File
@@ -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
}
+27
View File
@@ -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
}
+87
View File
@@ -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\n<?xml version="1.0"?><Value>OK</Value>' > "$TEST_FILE_FOLDER/$input"
run compare_data_with_xml_file "$TEST_FILE_FOLDER/$input" "<?xml version=\"1.0\"?>\n<Value>OK</Value>"
assert_success
}
@test "get_xml_data - valid XML without declaration" {
input=$(get_file_name)
printf 'HTTP/1.1 200\r\n\r\n<Value>AlsoOK</Value>' > "$TEST_FILE_FOLDER/$input"
run compare_data_with_xml_file "$TEST_FILE_FOLDER/$input" "<?xml version=\"1.0\"?>\n<Value>AlsoOK</Value>"
assert_success
}
@test "get_xml_data - XML with extra content after root" {
input=$(get_file_name)
printf 'HTTP/1.1 200\r\n\r\n<Value>AgainOK</Value>extra' > "$TEST_FILE_FOLDER/$input"
run compare_data_with_xml_file "$TEST_FILE_FOLDER/$input" "<?xml version=\"1.0\"?>\n<Value>AgainOK</Value>"
assert_success
}