الملفات
ghaymah-log-monitoring-project/test_api.sh
2025-10-07 07:29:01 +03:00

179 أسطر
5.7 KiB
Bash

#!/usr/bin/env bash
set -o errexit
set -o pipefail
set -o nounset
BASE_URL="${BASE_URL:-http://localhost:5004}"
TEST_FILE="${1:-test_cases.json}"
TIMEOUT="${2:-10}"
INSECURE="${INSECURE:-false}"
if ! command -v curl >/dev/null 2>&1; then
echo "ERROR: curl is required." >&2
exit 2
fi
if ! command -v jq >/dev/null 2>&1; then
echo "ERROR: jq is required." >&2
exit 2
fi
CURL_INSECURE_FLAG=""
if [[ "$INSECURE" == "true" ]]; then
CURL_INSECURE_FLAG="--insecure"
echo "Note: curl will run with --insecure (INSECURE=true)."
fi
if ! [[ "$BASE_URL" =~ ^https?:// ]]; then
echo "ERROR: BASE_URL must start with http:// or https://" >&2
exit 2
fi
if ! jq -e '.' "$TEST_FILE" >/dev/null 2>&1; then
echo "ERROR: cannot parse test file '$TEST_FILE' as JSON" >&2
exit 2
fi
idx=0
TOTAL=$(jq 'length' "$TEST_FILE")
jq -c '.[]' "$TEST_FILE" | while read -r test; do
idx=$((idx+1))
name=$(jq -r '.name // "test_'$idx'"' <<<"$test")
method=$(jq -r '.method // "GET"' <<<"$test")
endpoint=$(jq -r '.url' <<<"$test")
url="${BASE_URL}${endpoint}"
headers_json=$(jq -r '.headers // {}' <<<"$test")
body=$(jq -r 'if has("body") then .body else empty end' <<<"$test")
expect_status=$(jq -r '.expect_status // "2xx"' <<<"$test")
expect_contains=$(jq -r '.expect_contains // ""' <<<"$test")
max_latency_ms=$(jq -r '.max_latency_ms // 5000' <<<"$test")
retries=$(jq -r '.retries // 0' <<<"$test")
follow_location=$(jq -r '.follow_location // false' <<<"$test")
encoded_body=""
if [[ -n "$body" ]]; then
if ! encoded_body=$(jq -r '@json' <<<"$body" 2>/dev/null); then
echo "[$idx/$TOTAL] $name -> ERROR: Invalid JSON body" >&2
continue
fi
fi
CURL_HDR_ARGS=()
if jq -e 'type=="object"' <<<"$headers_json" >/dev/null 2>&1; then
while IFS= read -r h; do
kv=$(echo "$h" | base64 --decode)
k=$(jq -r '.key' <<<"$kv")
v=$(jq -r '.value' <<<"$kv")
CURL_HDR_ARGS+=( -H "$k: $v" )
done < <(echo "$headers_json" | jq -r 'to_entries[] | @base64')
fi
CURL_FOLLOW_FLAG=""
if [[ "$follow_location" == "true" ]]; then
CURL_FOLLOW_FLAG="-L"
fi
temp_resp=$(mktemp)
attempt=0
success=false
last_err=""
http_code="0"
latency_ms=0
bytes=0
use_insecure=false
while (( attempt <= retries )); do
attempt=$((attempt+1))
set +o errexit
curl_out=$(curl -sS -w '\n%{http_code} %{time_total} %{size_download}' \
-X "$method" "${CURL_HDR_ARGS[@]}" ${use_insecure:+$CURL_INSECURE_FLAG} $CURL_FOLLOW_FLAG \
--max-time "$TIMEOUT" \
${encoded_body:+--data-raw} ${encoded_body:+"$encoded_body"} \
-D - \
--output "$temp_resp" \
"$url" 2>&1) || CURL_EXIT=$? && CURL_EXIT=${CURL_EXIT:-0}
set -o errexit
lastline=$(printf "%s" "$curl_out" | tail -n1)
http_code=$(awk '{print $1}' <<<"$lastline")
time_total=$(awk '{print $2}' <<<"$lastline")
bytes=$(awk '{print $3}' <<<"$lastline")
latency_ms=$(awk "BEGIN {printf \"%d\", $time_total * 1000}")
if [[ "$CURL_EXIT" -ne 0 ]]; then
last_err="curl_failed_exit_${CURL_EXIT}: $(printf '%s' "$curl_out" | head -n1)"
# Retry with --insecure for SSL errors
if [[ "$CURL_EXIT" -eq 56 && "$use_insecure" == "false" ]]; then
echo "[$idx/$TOTAL] $name attempt $attempt: SSL error detected, retrying with --insecure" >&2
use_insecure=true
continue
fi
echo "[$idx/$TOTAL] $name attempt $attempt: curl error ($CURL_EXIT): $last_err" >&2
if (( attempt <= retries )); then sleep 1; continue; else break; fi
fi
ok_status=false
if [[ "$expect_status" =~ ^[0-9]{3}$ ]]; then
[[ "$http_code" == "$expect_status" ]] && ok_status=true
elif [[ "$expect_status" =~ ^[23]xx$ ]]; then
prefix=${expect_status:0:1}
if [[ "$http_code" =~ ^$prefix[0-9][0-9]$ ]]; then ok_status=true; fi
fi
if [[ "$CURL_EXIT" -ne 0 && "$http_code" == "$expect_status" ]]; then
CURL_EXIT=0
last_err=""
fi
contains_ok=true
if [[ -n "$expect_contains" ]]; then
if ! grep -qF "$expect_contains" "$temp_resp"; then
contains_ok=false
last_err="missing_expected_text: expected '$expect_contains' not found in response"
fi
fi
if [[ "$ok_status" == "true" && "$contains_ok" == "true" && "$latency_ms" -le "$max_latency_ms" ]]; then
success=true
last_err=""
break
else
reasons=()
[[ "$ok_status" != "true" ]] && reasons+=("unexpected_status:${http_code}, expected:${expect_status}")
[[ "$contains_ok" != "true" ]] && reasons+=("missing_expected_text: expected '$expect_contains' not found")
[[ "$latency_ms" -gt "$max_latency_ms" ]] && reasons+=("slow_response:${latency_ms}ms>${max_latency_ms}ms")
last_err=$(IFS=','; echo "${reasons[*]}")
echo "[$idx/$TOTAL] $name attempt $attempt: FAILED - $last_err" >&2
if [[ -s "$temp_resp" ]]; then
echo "Response body:" >&2
cat "$temp_resp" >&2
fi
fi
done
result=$(jq -n \
--arg name "$name" \
--arg url "$url" \
--arg method "$method" \
--arg http_code "$http_code" \
--argjson success "$([[ "$success" == true ]] && echo true || echo false)" \
--arg latency_ms "$latency_ms" \
--arg bytes "$bytes" \
--arg last_err "$last_err" \
'{name:$name, url:$url, method:$method, http_code:($http_code|tonumber), success:$success, latency_ms:($latency_ms|tonumber), bytes:($bytes|tonumber), last_err:$last_err}')
if [[ "$success" == true ]]; then
echo "[$idx/$TOTAL] $name -> OK (status $http_code, ${latency_ms}ms)"
else
echo "[$idx/$TOTAL] $name -> FAIL (status ${http_code:-n/a}, reason: $last_err)" >&2
if [[ -s "$temp_resp" ]]; then
echo "Response body:" >&2
cat "$temp_resp" >&2
fi
fi
rm -f "$temp_resp"
done