399 lines
No EOL
10 KiB
Bash
399 lines
No EOL
10 KiB
Bash
#!/bin/bash
|
|
# HTTP Test Assertions Library
|
|
# Provides reusable assertion functions for HTTP testing
|
|
|
|
set -euo pipefail
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Test counters
|
|
TESTS_RUN=0
|
|
TESTS_PASSED=0
|
|
TESTS_FAILED=0
|
|
|
|
# Log functions
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $*" >&2
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $*" >&2
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $*" >&2
|
|
}
|
|
|
|
# Core assertion failure handler
|
|
fail() {
|
|
local message="$1"
|
|
local context="${2:-}"
|
|
TESTS_FAILED=$((TESTS_FAILED + 1))
|
|
log_error "ASSERTION FAILED: $message"
|
|
if [ -n "$context" ]; then
|
|
log_error "Context: $context"
|
|
fi
|
|
# Don't exit immediately, let the test continue
|
|
return 1
|
|
}
|
|
|
|
pass() {
|
|
local message="$1"
|
|
TESTS_PASSED=$((TESTS_PASSED + 1))
|
|
if [ "${VERBOSE:-false}" = "true" ]; then
|
|
log_info "✓ $message"
|
|
fi
|
|
}
|
|
|
|
# HTTP Status assertions
|
|
assert_status() {
|
|
local actual="$1"
|
|
local expected="$2"
|
|
local context="${3:-HTTP request}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
if [ "$actual" -eq "$expected" ]; then
|
|
pass "Status code $expected for $context"
|
|
return 0
|
|
else
|
|
fail "Expected status $expected but got $actual" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
assert_status_range() {
|
|
local actual="$1"
|
|
local min="$2"
|
|
local max="$3"
|
|
local context="${4:-HTTP request}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
if [ "$actual" -ge "$min" ] && [ "$actual" -le "$max" ]; then
|
|
pass "Status code $actual in range [$min-$max] for $context"
|
|
return 0
|
|
else
|
|
fail "Expected status in range [$min-$max] but got $actual" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# JSON assertions
|
|
assert_json_has() {
|
|
local json="$1"
|
|
local path="$2"
|
|
local context="${3:-JSON response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
if echo "$json" | jq -e "$path" >/dev/null 2>&1; then
|
|
pass "JSON has path '$path' in $context"
|
|
return 0
|
|
else
|
|
fail "JSON missing path: $path" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
assert_json_equals() {
|
|
local json="$1"
|
|
local path="$2"
|
|
local expected="$3"
|
|
local context="${4:-JSON response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
local actual
|
|
actual=$(echo "$json" | jq -r "$path" 2>/dev/null || echo "")
|
|
|
|
if [ "$actual" = "$expected" ]; then
|
|
pass "JSON path '$path' equals '$expected' in $context"
|
|
return 0
|
|
else
|
|
fail "JSON path '$path' expected '$expected' but got '$actual'" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
assert_json_type() {
|
|
local json="$1"
|
|
local path="$2"
|
|
local expected_type="$3"
|
|
local context="${4:-JSON response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
local actual_type
|
|
actual_type=$(echo "$json" | jq -r "$path | type" 2>/dev/null || echo "null")
|
|
|
|
if [ "$actual_type" = "$expected_type" ]; then
|
|
pass "JSON path '$path' has type '$expected_type' in $context"
|
|
return 0
|
|
else
|
|
fail "JSON path '$path' expected type '$expected_type' but got '$actual_type'" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
assert_json_not_empty() {
|
|
local json="$1"
|
|
local path="$2"
|
|
local context="${3:-JSON response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
local value
|
|
value=$(echo "$json" | jq -r "$path" 2>/dev/null || echo "null")
|
|
|
|
if [ "$value" != "null" ] && [ "$value" != "" ] && [ "$value" != "[]" ] && [ "$value" != "{}" ]; then
|
|
pass "JSON path '$path' is not empty in $context"
|
|
return 0
|
|
else
|
|
fail "JSON path '$path' is empty or null" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Header assertions
|
|
assert_header_exists() {
|
|
local headers="$1"
|
|
local header_name="$2"
|
|
local context="${3:-HTTP response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
if echo "$headers" | grep -qi "^$header_name:"; then
|
|
pass "Header '$header_name' exists in $context"
|
|
return 0
|
|
else
|
|
fail "Header '$header_name' missing" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
assert_header_contains() {
|
|
local headers="$1"
|
|
local header_name="$2"
|
|
local expected_value="$3"
|
|
local context="${4:-HTTP response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
local header_line
|
|
header_line=$(echo "$headers" | grep -i "^$header_name:" || echo "")
|
|
|
|
if echo "$header_line" | grep -qi "$expected_value"; then
|
|
pass "Header '$header_name' contains '$expected_value' in $context"
|
|
return 0
|
|
else
|
|
fail "Header '$header_name' does not contain '$expected_value'" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
assert_header_equals() {
|
|
local headers="$1"
|
|
local header_name="$2"
|
|
local expected_value="$3"
|
|
local context="${4:-HTTP response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
local actual_value
|
|
actual_value=$(echo "$headers" | grep -i "^$header_name:" | cut -d' ' -f2- | tr -d '\r\n' || echo "")
|
|
|
|
if [ "$actual_value" = "$expected_value" ]; then
|
|
pass "Header '$header_name' equals '$expected_value' in $context"
|
|
return 0
|
|
else
|
|
fail "Header '$header_name' expected '$expected_value' but got '$actual_value'" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Performance assertions
|
|
assert_latency_lt() {
|
|
local actual_ms="$1"
|
|
local threshold_ms="$2"
|
|
local context="${3:-HTTP request}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
# Use bc for floating point comparison if available, otherwise awk
|
|
if command -v bc >/dev/null 2>&1; then
|
|
if [ "$(echo "$actual_ms < $threshold_ms" | bc)" -eq 1 ]; then
|
|
pass "Latency ${actual_ms}ms < ${threshold_ms}ms for $context"
|
|
return 0
|
|
else
|
|
fail "Latency ${actual_ms}ms >= ${threshold_ms}ms" "$context"
|
|
return 1
|
|
fi
|
|
else
|
|
if awk -v a="$actual_ms" -v b="$threshold_ms" 'BEGIN{exit !(a<b)}'; then
|
|
pass "Latency ${actual_ms}ms < ${threshold_ms}ms for $context"
|
|
return 0
|
|
else
|
|
fail "Latency ${actual_ms}ms >= ${threshold_ms}ms" "$context"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Content assertions
|
|
assert_body_contains() {
|
|
local body="$1"
|
|
local expected="$2"
|
|
local context="${3:-response body}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
if echo "$body" | grep -q "$expected"; then
|
|
pass "Body contains '$expected' in $context"
|
|
return 0
|
|
else
|
|
fail "Body does not contain '$expected'" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
assert_body_not_contains() {
|
|
local body="$1"
|
|
local unexpected="$2"
|
|
local context="${3:-response body}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
if echo "$body" | grep -q "$unexpected"; then
|
|
fail "Body contains unexpected '$unexpected'" "$context"
|
|
return 1
|
|
else
|
|
pass "Body does not contain '$unexpected' in $context"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Security assertions
|
|
assert_security_headers() {
|
|
local headers="$1"
|
|
local context="${2:-HTTP response}"
|
|
local all_passed=true
|
|
|
|
# Check for essential security headers
|
|
local security_headers=(
|
|
"X-Content-Type-Options"
|
|
"X-Frame-Options"
|
|
"Referrer-Policy"
|
|
"Content-Security-Policy"
|
|
)
|
|
|
|
for header in "${security_headers[@]}"; do
|
|
if ! assert_header_exists "$headers" "$header" "$context"; then
|
|
all_passed=false
|
|
fi
|
|
done
|
|
|
|
if [ "$all_passed" = true ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Cookie assertions
|
|
assert_cookie_exists() {
|
|
local headers="$1"
|
|
local cookie_name="$2"
|
|
local context="${3:-HTTP response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
if echo "$headers" | grep -i "^set-cookie:" | grep -q "$cookie_name="; then
|
|
pass "Cookie '$cookie_name' exists in $context"
|
|
return 0
|
|
else
|
|
fail "Cookie '$cookie_name' not found" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
assert_cookie_has_flag() {
|
|
local headers="$1"
|
|
local cookie_name="$2"
|
|
local flag="$3"
|
|
local context="${4:-HTTP response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
local cookie_line
|
|
cookie_line=$(echo "$headers" | grep -i "^set-cookie:" | grep "$cookie_name=" || echo "")
|
|
|
|
if echo "$cookie_line" | grep -qi "$flag"; then
|
|
pass "Cookie '$cookie_name' has flag '$flag' in $context"
|
|
return 0
|
|
else
|
|
fail "Cookie '$cookie_name' missing flag '$flag'" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test summary
|
|
print_test_summary() {
|
|
echo
|
|
echo "=========================================="
|
|
echo "Test Summary"
|
|
echo "=========================================="
|
|
echo "Total tests run: $TESTS_RUN"
|
|
echo -e "Passed: ${GREEN}$TESTS_PASSED${NC}"
|
|
echo -e "Failed: ${RED}$TESTS_FAILED${NC}"
|
|
echo "=========================================="
|
|
|
|
if [ "$TESTS_FAILED" -gt 0 ]; then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Utility functions
|
|
url_encode() {
|
|
local string="$1"
|
|
echo -n "$string" | jq -sRr @uri
|
|
}
|
|
|
|
generate_test_email() {
|
|
local prefix="${TEST_EMAIL_PREFIX:-user+test}"
|
|
local domain="${TEST_EMAIL_DOMAIN:-lab.veza}"
|
|
local rand="${1:-$(date +%s)}"
|
|
echo "${prefix}${rand}@${domain}"
|
|
}
|
|
|
|
generate_test_password() {
|
|
local prefix="${TEST_PASSWORD_PREFIX:-V3za!lab-}"
|
|
local rand="${1:-$(date +%s)}"
|
|
echo "${prefix}${rand}"
|
|
}
|
|
|
|
# JSON Schema validation (if ajv available)
|
|
assert_json_schema() {
|
|
local json="$1"
|
|
local schema_file="$2"
|
|
local context="${3:-JSON response}"
|
|
TESTS_RUN=$((TESTS_RUN + 1))
|
|
|
|
if ! command -v ajv >/dev/null 2>&1; then
|
|
log_warn "ajv not installed, skipping schema validation"
|
|
return 0
|
|
fi
|
|
|
|
if echo "$json" | ajv validate -s "$schema_file" >/dev/null 2>&1; then
|
|
pass "JSON validates against schema in $context"
|
|
return 0
|
|
else
|
|
fail "JSON schema validation failed" "$context"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Export all functions
|
|
export -f log_info log_warn log_error fail pass
|
|
export -f assert_status assert_status_range
|
|
export -f assert_json_has assert_json_equals assert_json_type assert_json_not_empty
|
|
export -f assert_header_exists assert_header_contains assert_header_equals
|
|
export -f assert_latency_lt
|
|
export -f assert_body_contains assert_body_not_contains
|
|
export -f assert_security_headers
|
|
export -f assert_cookie_exists assert_cookie_has_flag
|
|
export -f print_test_summary
|
|
export -f url_encode generate_test_email generate_test_password
|
|
export -f assert_json_schema |