diff --git a/tests/commands/complete_multipart_upload.sh b/tests/commands/complete_multipart_upload.sh
index 62f90415..8958e154 100644
--- a/tests/commands/complete_multipart_upload.sh
+++ b/tests/commands/complete_multipart_upload.sh
@@ -14,6 +14,8 @@
# specific language governing permissions and limitations
# under the License.
+source ./tests/drivers/list_parts/list_parts_rest.sh
+
complete_multipart_upload() {
if [[ $# -ne 4 ]]; then
log 2 "'complete multipart upload' command requires bucket, key, upload ID, parts list"
@@ -100,4 +102,4 @@ complete_multipart_upload_rest_invalid_checksum() {
log 2 "expected '400', was $result: $(cat "$TEST_FILE_FOLDER/result.txt")"
return 1
fi
-}
\ No newline at end of file
+}
diff --git a/tests/commands/delete_object.sh b/tests/commands/delete_object.sh
index ce4551ab..ffc3c761 100644
--- a/tests/commands/delete_object.sh
+++ b/tests/commands/delete_object.sh
@@ -139,6 +139,7 @@ delete_object_with_user() {
fi
if [ $exit_code -ne 0 ]; then
log 2 "error deleting object: $delete_object_error"
+ echo "$delete_object_error" >&2
return 1
fi
return 0
diff --git a/tests/drivers/complete_multipart_upload/complete_multipart_upload_rest.sh b/tests/drivers/complete_multipart_upload/complete_multipart_upload_rest.sh
index 5ca91773..23d2fe8e 100644
--- a/tests/drivers/complete_multipart_upload/complete_multipart_upload_rest.sh
+++ b/tests/drivers/complete_multipart_upload/complete_multipart_upload_rest.sh
@@ -179,3 +179,62 @@ complete_multipart_upload_invalid_object_size_string() {
fi
return 0
}
+
+perform_multipart_upload_rest() {
+ if ! check_param_count_v2 "bucket, key, four parts" 6 $#; then
+ return 1
+ fi
+ if ! upload_id=$(create_multipart_upload_rest "$1" "$2" "" "parse_upload_id" 2>&1); then
+ log 2 "error creating multipart upload: $upload_id"
+ return 1
+ fi
+ if ! etag=$(upload_part_rest "$1" "$2" "$upload_id" 1 "$3" 2>&1); then
+ log 2 "error uploading part 1"
+ return 1
+ fi
+ parts_payload="$etag1"
+ if ! etag=$(upload_part_rest "$1" "$2" "$upload_id" 2 "$4" 2>&1); then
+ log 2 "error uploading part 2: $etag"
+ return 1
+ fi
+ parts_payload+="$etag2"
+ if ! etag=$(upload_part_rest "$1" "$2" "$upload_id" 3 "$5" 2>&1); then
+ log 2 "error uploading part 3: $etag"
+ return 1
+ fi
+ parts_payload+="$etag3"
+ if ! etag=$(upload_part_rest "$1" "$2" "$upload_id" 4 "$6" 2>&1); then
+ log 2 "error uploading part 4: $etag"
+ return 1
+ fi
+ parts_payload+="$etag4"
+ if ! complete_multipart_upload_rest "$1" "$2" "$upload_id" "$parts_payload"; then
+ log 2 "error completing multipart upload"
+ return 1
+ fi
+ return 0
+}
+
+upload_check_parts() {
+ if ! check_param_count_v2 "bucket, key, list of 4 parts" 6 $#; then
+ return 1
+ fi
+ if ! upload_id=$(create_multipart_upload_rest "$1" "$2" "" "parse_upload_id" 2>&1); then
+ log 2 "error creating upload: $upload_id"
+ return 1
+ fi
+ if ! check_part_list_rest "$1" "$2" "$upload_id" 0; then
+ log 2 "error checking part list before part upload"
+ return 1
+ fi
+ if ! parts_payload=$(upload_each_part_and_check "$1" "$2" "$upload_id" "${@:3}" 2>&1); then
+ log 2 "error uploading and checking parts: $parts_payload"
+ return 1
+ fi
+ log 5 "PARTS PAYLOAD: $parts_payload"
+ if ! complete_multipart_upload_rest "$1" "$2" "$upload_id" "$parts_payload"; then
+ log 2 "error completing multipart upload"
+ return 1
+ fi
+ return 0
+}
diff --git a/tests/drivers/delete_object/delete_object_s3api.sh b/tests/drivers/delete_object/delete_object_s3api.sh
new file mode 100644
index 00000000..36f80e4f
--- /dev/null
+++ b/tests/drivers/delete_object/delete_object_s3api.sh
@@ -0,0 +1,31 @@
+#!/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.
+
+block_delete_object_without_permission() {
+ if ! check_param_count_v2 "bucket, file, username, password" 4 $#; then
+ return 1
+ fi
+ if delete_object_error=$(delete_object_with_user "s3api" "$1" "$2" "$3" "$4" 2>&1); then
+ log 2 "able to delete object despite lack of permissions"
+ return 1
+ fi
+ # shellcheck disable=SC2154
+ if [[ "$delete_object_error" != *"AccessDenied"* ]]; then
+ log 2 "invalid delete object error: $delete_object_error"
+ return 1
+ fi
+ return 0
+}
diff --git a/tests/drivers/delete_objects/delete_objects_rest.sh b/tests/drivers/delete_objects/delete_objects_rest.sh
new file mode 100644
index 00000000..2e55fc22
--- /dev/null
+++ b/tests/drivers/delete_objects/delete_objects_rest.sh
@@ -0,0 +1,109 @@
+#!/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.
+
+delete_object_empty_bucket_check_error() {
+ if ! file_name=$(get_file_name 2>&1); then
+ log 2 "error getting file name: $file_name"
+ return 1
+ fi
+ if ! result=$(OUTPUT_FILE="$TEST_FILE_FOLDER/$file_name" COMMAND_LOG="$COMMAND_LOG" BUCKET_NAME="" ./tests/rest_scripts/delete_objects.sh); then
+ log 2 "error deleting objects: $result"
+ return 1
+ fi
+ log 5 "result: $(cat "$TEST_FILE_FOLDER/$file_name")"
+ if ! error=$(xmllint --xpath "Error" "$TEST_FILE_FOLDER/$file_name" 2>&1); then
+ log 2 "error getting XML error data: $error"
+ return 1
+ fi
+ if ! error_file_name=$(get_file_name 2>&1); then
+ log 2 "error getting error file name: $file_name"
+ return 1
+ fi
+ echo -n "$error" > "$TEST_FILE_FOLDER/$error_file_name"
+ if ! check_xml_element "$TEST_FILE_FOLDER/$error_file_name" "MethodNotAllowed" "Code"; then
+ log 2 "Code mismatch"
+ return 1
+ fi
+ if ! check_xml_element "$TEST_FILE_FOLDER/$error_file_name" "POST" "Method"; then
+ log 2 "Method mismatch"
+ return 1
+ fi
+ if ! check_xml_element "$TEST_FILE_FOLDER/$error_file_name" "SERVICE" "ResourceType"; then
+ log 2 "ResourceType mismatch"
+ return 1
+ fi
+ return 0
+}
+
+delete_objects_no_content_md5_header() {
+ if ! check_param_count_v2 "bucket name" 1 $#; then
+ return 1
+ fi
+ data="
+
+
+ "
+
+ if ! file_name=$(get_file_name 2>&1); then
+ log 2 "error getting file name: $file_name"
+ return 1
+ fi
+ if ! result=$(OUTPUT_FILE="$TEST_FILE_FOLDER/$file_name" COMMAND_LOG="$COMMAND_LOG" PAYLOAD="$data" BUCKET_NAME="$1" HAS_CONTENT_MD5="false" ./tests/rest_scripts/delete_objects.sh); then
+ log 2 "error deleting objects: $result"
+ return 1
+ fi
+ if [ "$result" != "400" ]; then
+ log 2 "expected response code '400', actual '$result' ($(cat "$TEST_FILE_FOLDER/$file_name")"
+ return 1
+ fi
+ if ! check_xml_element "$TEST_FILE_FOLDER/$file_name" "InvalidRequest" "Error" "Code"; then
+ log 2 "error checking error element"
+ return 1
+ fi
+ return 0
+}
+
+delete_objects_verify_success() {
+ if ! check_param_count_v2 "bucket name, two object names" 3 $#; then
+ return 1
+ fi
+ data="
+
+
+"
+
+ if ! file_name=$(get_file_name 2>&1); then
+ log 2 "error getting file name: $file_name"
+ return 1
+ fi
+ if ! result=$(OUTPUT_FILE="$TEST_FILE_FOLDER/$file_name" COMMAND_LOG="$COMMAND_LOG" PAYLOAD="$data" BUCKET_NAME="$1" ./tests/rest_scripts/delete_objects.sh); then
+ log 2 "error deleting objects: $result"
+ return 1
+ fi
+ if [ "$result" != "200" ]; then
+ log 2 "expected '200', was '$result ($(cat "$TEST_FILE_FOLDER/$file_name"))"
+ return 1
+ fi
+ return 0
+}
diff --git a/tests/drivers/get_object_attributes/get_object_attributes_rest.sh b/tests/drivers/get_object_attributes/get_object_attributes_rest.sh
index c597b029..6b2f1d2c 100644
--- a/tests/drivers/get_object_attributes/get_object_attributes_rest.sh
+++ b/tests/drivers/get_object_attributes/get_object_attributes_rest.sh
@@ -14,8 +14,6 @@
# specific language governing permissions and limitations
# under the License.
-source ./tests/util/util_list_parts.sh
-
upload_and_check_attributes() {
if ! check_param_count_v2 "bucket, test file, file size" 3 $#; then
return 1
diff --git a/tests/drivers/list_parts/list_parts_rest.sh b/tests/drivers/list_parts/list_parts_rest.sh
new file mode 100644
index 00000000..918ed8c0
--- /dev/null
+++ b/tests/drivers/list_parts/list_parts_rest.sh
@@ -0,0 +1,131 @@
+#!/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.
+
+check_part_list_rest_etags() {
+ if ! check_param_count_gt "data file name, etags" 2 $#; then
+ return 1
+ fi
+ if ! etags_raw=$(xmllint --xpath '//*[local-name()="ETag"]/text()' "$1" 2>&1); then
+ log 2 "error retrieving etags: $etags"
+ return 1
+ fi
+ etags=$(echo "$etags_raw" | tr '\n' ' ')
+ read -ra etags_array <<< "$etags"
+
+ idx=0
+ shift
+ if [ $# -ne ${#etags_array[@]} ]; then
+ log 2 "expected tag size of '$#', actual ${#etags_array[@]}"
+ return 1
+ fi
+ while [ $# -gt 0 ]; do
+ if [ "$1" != "${etags_array[$idx]}" ]; then
+ log 2 "etag mismatch (expected '$1', actual ${etags_array[$idx]})"
+ return 1
+ fi
+ ((idx++))
+ shift
+ done
+ return 0
+}
+
+check_part_list_rest() {
+ if ! check_param_count_gt "bucket, key name, upload ID, expected etag count, expected etags" 4 $#; then
+ return 1
+ fi
+ if ! file_name=$(get_file_name 2>&1); then
+ log 2 "error getting file name: $file_name"
+ return 1
+ fi
+ if ! result=$(COMMAND_LOG="$COMMAND_LOG" BUCKET_NAME="$1" OBJECT_KEY="$2" UPLOAD_ID="$3" OUTPUT_FILE="$TEST_FILE_FOLDER/$file_name" ./tests/rest_scripts/list_parts.sh); then
+ log 2 "error listing multipart upload parts: $result"
+ return 1
+ fi
+ if [ "$result" != "200" ]; then
+ log 2 "list-parts returned response code: $result, reply: $(cat "$TEST_FILE_FOLDER/$file_name")"
+ return 1
+ fi
+ log 5 "parts list: $(cat "$TEST_FILE_FOLDER/$file_name")"
+ if ! parts_upload_id=$(xmllint --xpath '//*[local-name()="UploadId"]/text()' "$TEST_FILE_FOLDER/$file_name" 2>&1); then
+ log 2 "error retrieving UploadId: $parts_upload_id"
+ return 1
+ fi
+ if [ "$parts_upload_id" != "$3" ]; then
+ log 2 "expected '$3', UploadId value is '$parts_upload_id'"
+ return 1
+ fi
+ if ! part_count=$(xmllint --xpath 'count(//*[local-name()="Part"])' "$TEST_FILE_FOLDER/$file_name" 2>&1); then
+ log 2 "error retrieving part count: $part_count"
+ return 1
+ fi
+ if [ "$part_count" != "$4" ]; then
+ log 2 "expected $4, 'Part' count is '$part_count'"
+ return 1
+ fi
+ if [ "$4" == 0 ]; then
+ return 0
+ fi
+ if ! check_part_list_rest_etags "$TEST_FILE_FOLDER/$file_name" "${@:5}"; then
+ log 2 "error checking etags"
+ return 1
+ fi
+ return 0
+}
+
+upload_each_part_and_check() {
+ if ! check_param_count_gt "bucket, key, upload ID, parts" 5 $#; then
+ return 1
+ fi
+
+ local part_count=$(($#-3))
+ local parts_payload=""
+ local etags=()
+
+ for ((idx=1; idx<=part_count; idx++)); do
+ local payload_section=""
+ part_number=$((idx+3))
+
+ if ! etag_and_payload_section=$(upload_check_part "$1" "$2" "$3" "$idx" "${!part_number}" "${etags[@]}" 2>&1); then
+ log 2 "error uploading and checking part '$idx': $etag_and_payload_section"
+ return 1
+ fi
+
+ IFS=$'\n' read -r -d '' etag payload_section < <(printf '%s\0' "$etag_and_payload_section")
+ parts_payload+="$payload_section"
+ etags+=("$etag")
+ done
+ echo "$parts_payload"
+ return 0
+}
+
+upload_check_part() {
+ if ! check_param_count_gt "bucket, key, upload ID, part number, part, etags (if any)" 5 $#; then
+ return 1
+ fi
+ if ! etag=$(upload_part_rest "$1" "$2" "$3" "$4" "$5" 2>&1); then
+ log 2 "error uploading part '$4': $etag"
+ return 1
+ fi
+ local payload_part="$etag$4"
+ # shellcheck disable=SC2068
+ if ! check_part_list_rest "$1" "$2" "$3" "$4" "${@:6}" "$etag"; then
+ log 2 "error checking part list after upload $4"
+ return 1
+ fi
+ echo "$etag"
+ echo "$payload_part"
+ return 0
+}
diff --git a/tests/env.sh b/tests/env.sh
index af4c3824..58571abf 100644
--- a/tests/env.sh
+++ b/tests/env.sh
@@ -16,6 +16,12 @@
source ./tests/versity.sh
+if [ -n "$BASH_VERSION" ] && [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
+ echo "ERROR: This test suite requires bash 4.0 or later. Current: $BASH_VERSION" >&2
+ echo "On macOS: brew install bash && add to PATH" >&2
+ exit 1
+fi
+
base_setup() {
check_env_vars
if [ "$RUN_VERSITYGW" == "true" ] && [ "$UNIT_TEST" != "true" ]; then
diff --git a/tests/test_rest_bucket.sh b/tests/test_rest_bucket.sh
index b2765b5c..dba4630a 100755
--- a/tests/test_rest_bucket.sh
+++ b/tests/test_rest_bucket.sh
@@ -29,7 +29,6 @@ source ./tests/drivers/put_bucket_ownership_controls/put_bucket_ownership_contro
source ./tests/drivers/put_bucket_tagging/put_bucket_tagging_rest.sh
source ./tests/logger.sh
source ./tests/setup.sh
-source ./tests/util/util_delete_object.sh
source ./tests/util/util_list_buckets.sh
source ./tests/util/util_lock_config.sh
source ./tests/util/util_public_access_block.sh
diff --git a/tests/test_rest_delete_object.sh b/tests/test_rest_delete_object.sh
index b7336000..0d8a18c8 100755
--- a/tests/test_rest_delete_object.sh
+++ b/tests/test_rest_delete_object.sh
@@ -18,7 +18,7 @@ load ./bats-support/load
load ./bats-assert/load
source ./tests/drivers/create_bucket/create_bucket_rest.sh
-source ./tests/util/util_delete_object.sh
+source ./tests/drivers/delete_objects/delete_objects_rest.sh
source ./tests/setup.sh
@test "test_rest_delete_object" {
diff --git a/tests/test_rest_multipart.sh b/tests/test_rest_multipart.sh
index f149721e..57f818cf 100755
--- a/tests/test_rest_multipart.sh
+++ b/tests/test_rest_multipart.sh
@@ -25,7 +25,6 @@ source ./tests/drivers/complete_multipart_upload/complete_multipart_upload_rest.
source ./tests/drivers/list_buckets/list_buckets_rest.sh
source ./tests/drivers/upload_part/upload_part_rest.sh
source ./tests/util/util_file.sh
-source ./tests/util/util_list_parts.sh
test_file="test_file"
diff --git a/tests/test_s3api_policy_object.sh b/tests/test_s3api_policy_object.sh
index 1eb80963..793eea95 100644
--- a/tests/test_s3api_policy_object.sh
+++ b/tests/test_s3api_policy_object.sh
@@ -14,8 +14,8 @@
# specific language governing permissions and limitations
# under the License.
+source ./tests/drivers/delete_object/delete_object_s3api.sh
source ./tests/drivers/put_bucket_policy/put_bucket_policy.sh
-source ./tests/util/util_delete_object.sh
test_s3api_policy_allow_deny() {
policy_file="policy_file"
diff --git a/tests/util/util_delete_object.sh b/tests/util/util_delete_object.sh
deleted file mode 100644
index d05de529..00000000
--- a/tests/util/util_delete_object.sh
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env bash
-
-block_delete_object_without_permission() {
- if [ $# -ne 4 ]; then
- log 2 "'attempt_delete_object_without_permission' requires bucket, file, username, password"
- return 1
- fi
- if delete_object_with_user "s3api" "$1" "$2" "$3" "$4"; then
- log 2 "able to delete object despite lack of permissions"
- return 1
- fi
- # shellcheck disable=SC2154
- if [[ "$delete_object_error" != *"AccessDenied"* ]]; then
- log 2 "invalid delete object error: $delete_object_error"
- return 1
- fi
- return 0
-}
-
-delete_object_empty_bucket_check_error() {
- if ! result=$(OUTPUT_FILE="$TEST_FILE_FOLDER/result.txt" COMMAND_LOG="$COMMAND_LOG" BUCKET_NAME="" ./tests/rest_scripts/delete_objects.sh); then
- log 2 "error deleting objects: $result"
- return 1
- fi
- log 5 "result: $(cat "$TEST_FILE_FOLDER/result.txt")"
- if ! error=$(xmllint --xpath "Error" "$TEST_FILE_FOLDER/result.txt" 2>&1); then
- log 2 "error getting XML error data: $error"
- return 1
- fi
- echo -n "$error" > "$TEST_FILE_FOLDER/error.txt"
- if ! check_xml_element "$TEST_FILE_FOLDER/error.txt" "MethodNotAllowed" "Code"; then
- log 2 "Code mismatch"
- return 1
- fi
- if ! check_xml_element "$TEST_FILE_FOLDER/error.txt" "POST" "Method"; then
- log 2 "Method mismatch"
- return 1
- fi
- if ! check_xml_element "$TEST_FILE_FOLDER/error.txt" "SERVICE" "ResourceType"; then
- log 2 "ResourceType mismatch"
- return 1
- fi
- return 0
-}
-
-delete_objects_no_content_md5_header() {
- if [ $# -ne 1 ]; then
- log 2 "delete_objects_no_content_md5_header requires bucket name"
- return 1
- fi
- data="
-
-
- "
-
- if ! result=$(OUTPUT_FILE="$TEST_FILE_FOLDER/result.txt" COMMAND_LOG="$COMMAND_LOG" PAYLOAD="$data" BUCKET_NAME="$1" HAS_CONTENT_MD5="false" ./tests/rest_scripts/delete_objects.sh); then
- log 2 "error deleting objects: $result"
- return 1
- fi
- if [ "$result" != "400" ]; then
- log 2 "expected response code '400', actual '$result' ($(cat "$TEST_FILE_FOLDER/result.txt")"
- return 1
- fi
- if ! check_xml_element "$TEST_FILE_FOLDER/result.txt" "InvalidRequest" "Error" "Code"; then
- log 2 "error checking error element"
- return 1
- fi
-}
-
-delete_objects_verify_success() {
- if [ $# -ne 3 ]; then
- log 2 "'delete_objects_verify_success' requires bucket name, two objects"
- return 1
- fi
- data="
-
-
-"
-
- if ! result=$(OUTPUT_FILE="$TEST_FILE_FOLDER/result.txt" COMMAND_LOG="$COMMAND_LOG" PAYLOAD="$data" BUCKET_NAME="$1" ./tests/rest_scripts/delete_objects.sh); then
- log 2 "error deleting objects: $result"
- return 1
- fi
- if [ "$result" != "200" ]; then
- log 2 "expected '200', was '$result ($(cat "$TEST_FILE_FOLDER/result.txt"))"
- return 1
- fi
- return 0
-}
diff --git a/tests/util/util_list_parts.sh b/tests/util/util_list_parts.sh
deleted file mode 100644
index 397e0c09..00000000
--- a/tests/util/util_list_parts.sh
+++ /dev/null
@@ -1,165 +0,0 @@
-#!/usr/bin/env bash
-
-check_part_list_rest_etags() {
- if ! etags=$(xmllint --xpath '//*[local-name()="ETag"]/text()' "$TEST_FILE_FOLDER/parts.txt" | tr '\n' ' ' 2>&1); then
- log 2 "error retrieving etags: $etags"
- return 1
- fi
- read -ra etags_array <<< "$etags"
- idx=0
- if [ $# -ne ${#etags_array[@]} ]; then
- log 2 "expected tag size of '$#', actual ${#etags_array[@]}"
- return 1
- fi
- while [ $# -gt 0 ]; do
- if [ "$1" != "${etags_array[$idx]}" ]; then
- log 2 "etag mismatch (expected '$1', actual ${etags_array[$idx]})"
- return 1
- fi
- ((idx++))
- shift
- done
- return 0
-}
-
-check_part_list_rest() {
- if [ $# -lt 4 ]; then
- log 2 "'check_part_list_rest' requires bucket, file name, upload ID, expected count, etags"
- return 1
- fi
- if ! result=$(COMMAND_LOG="$COMMAND_LOG" BUCKET_NAME="$1" OBJECT_KEY="$2" UPLOAD_ID="$3" OUTPUT_FILE="$TEST_FILE_FOLDER/parts.txt" ./tests/rest_scripts/list_parts.sh); then
- log 2 "error listing multipart upload parts: $result"
- return 1
- fi
- if [ "$result" != "200" ]; then
- log 2 "list-parts returned response code: $result, reply: $(cat "$TEST_FILE_FOLDER/parts.txt")"
- return 1
- fi
- log 5 "parts list: $(cat "$TEST_FILE_FOLDER/parts.txt")"
- if ! parts_upload_id=$(xmllint --xpath '//*[local-name()="UploadId"]/text()' "$TEST_FILE_FOLDER/parts.txt" 2>&1); then
- log 2 "error retrieving UploadId: $parts_upload_id"
- return 1
- fi
- if [ "$parts_upload_id" != "$3" ]; then
- log 2 "expected '$3', UploadId value is '$parts_upload_id'"
- return 1
- fi
- if ! part_count=$(xmllint --xpath 'count(//*[local-name()="Part"])' "$TEST_FILE_FOLDER/parts.txt" 2>&1); then
- log 2 "error retrieving part count: $part_count"
- return 1
- fi
- if [ "$part_count" != "$4" ]; then
- log 2 "expected $4, 'Part' count is '$part_count'"
- return 1
- fi
- if [ "$4" == 0 ]; then
- return 0
- fi
- if ! check_part_list_rest_etags "${@:5}"; then
- log 2 "error checking etags"
- return 1
- fi
- return 0
-}
-
-perform_multipart_upload_rest() {
- if [ $# -ne 6 ]; then
- log 2 "'upload_check_parts' requires bucket, key, part list (4 parts)"
- return 1
- fi
- if ! create_multipart_upload_rest "$1" "$2" "" "parse_upload_id"; then
- log 2 "error creating multipart upload"
- return 1
- fi
- # shellcheck disable=SC2154
- if ! etag=$(upload_part_rest "$1" "$2" "$upload_id" 1 "$3" 2>&1); then
- log 2 "error uploading part 1"
- return 1
- fi
- # shellcheck disable=SC2154
- parts_payload="$etag1"
- if ! etag=$(upload_part_rest "$1" "$2" "$upload_id" 2 "$4" 2>&1); then
- log 2 "error uploading part 2: $etag"
- return 1
- fi
- parts_payload+="$etag2"
- if ! etag=$(upload_part_rest "$1" "$2" "$upload_id" 3 "$5" 2>&1); then
- log 2 "error uploading part 3: $etag"
- return 1
- fi
- parts_payload+="$etag3"
- if ! etag=$(upload_part_rest "$1" "$2" "$upload_id" 4 "$6" 2>&1); then
- log 2 "error uploading part 4: $etag"
- return 1
- fi
- parts_payload+="$etag4"
- if ! complete_multipart_upload_rest "$1" "$2" "$upload_id" "$parts_payload"; then
- log 2 "error completing multipart upload"
- return 1
- fi
- return 0
-}
-
-upload_check_parts() {
- if [ $# -ne 6 ]; then
- log 2 "'upload_check_parts' requires bucket, key, part list"
- return 1
- fi
- if ! create_multipart_upload_rest "$1" "$2" "" "parse_upload_id"; then
- log 2 "error creating upload"
- return 1
- fi
- # shellcheck disable=SC2154
- if ! check_part_list_rest "$1" "$2" "$upload_id" 0; then
- log 2 "error checking part list before part upload"
- return 1
- fi
- sleep 5
- parts_payload=""
- if ! upload_check_part "$1" "$2" "$upload_id" 1 "$3"; then
- log 2 "error uploading and checking first part"
- return 1
- fi
- if ! list_multipart_uploads_rest "$1"; then
- log 2 "error listing uploads"
- return 1
- fi
- etag_one=$etag
- if ! upload_check_part "$1" "$2" "$upload_id" 2 "$4" "$etag_one"; then
- log 2 "error uploading and checking second part"
- return 1
- fi
- etag_two=$etag
- if ! upload_check_part "$1" "$2" "$upload_id" 3 "$5" "$etag_one" "$etag_two"; then
- log 2 "error uploading and checking third part"
- return 1
- fi
- etag_three=$etag
- if ! upload_check_part "$1" "$2" "$upload_id" 4 "$6" "$etag_one" "$etag_two" "$etag_three"; then
- log 2 "error uploading and checking fourth part"
- return 1
- fi
- log 5 "PARTS PAYLOAD: $parts_payload"
- if ! complete_multipart_upload_rest "$1" "$2" "$upload_id" "$parts_payload"; then
- log 2 "error completing multipart upload"
- return 1
- fi
- return 0
-}
-
-upload_check_part() {
- if [ $# -lt 5 ]; then
- log 2 "'upload_check_part' requires bucket, key, upload ID, part number, part, etags"
- return 1
- fi
- if ! etag=$(upload_part_rest "$1" "$2" "$3" "$4" "$5" 2>&1); then
- log 2 "error uploading part $4: $etag"
- return 1
- fi
- parts_payload+="$etag$4"
- # shellcheck disable=SC2068
- if ! check_part_list_rest "$1" "$2" "$3" "$4" "${@:6}" "$etag"; then
- log 2 "error checking part list after upload $4"
- return 1
- fi
-}