#!/bin/bash set -euo pipefail # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" TEST_DIR="${SCRIPT_DIR}/test_tmp" TEST_TOKEN="test-token-$(date +%s)" CONTAINER_NAME="sos-test-$(date +%s)" IMAGE="gitea.jde.nz/public/simple-object-server:latest" TEST_PORT="" SOS_TEST_HOST="" CLEANUP_NEEDED=false # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # Logging functions log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } log_warning() { echo -e "${YELLOW}[WARN]${NC} $1"; } die() { log_error "$@"; exit 1; } # Cleanup on exit cleanup() { [[ "$CLEANUP_NEEDED" != true ]] && return log_info "Cleaning up test environment..." docker stop "${CONTAINER_NAME}" &>/dev/null || true docker rm "${CONTAINER_NAME}" &>/dev/null || true rm -rf "${TEST_DIR}" log_info "Cleanup complete" } trap cleanup EXIT # Check dependencies check_dependencies() { log_info "Checking dependencies..." for cmd in docker jq curl; do command -v $cmd &>/dev/null || die "$cmd is required but not installed" done docker info &>/dev/null || die "Docker daemon is not running" log_info "All dependencies satisfied" } # Setup test environment setup_test_environment() { log_info "Setting up test environment..." CLEANUP_NEEDED=true # Create directories rm -rf "${TEST_DIR}" mkdir -p "${TEST_DIR}"/{test_files,config} # Generate hashed token for authentication log_info "Generating authentication token..." local hashed_token=$(docker run --rm "$IMAGE" /sos/hash_token "${TEST_TOKEN}" 2>/dev/null | grep '^\$2[aby]\$' | head -1) [[ -z "$hashed_token" ]] && die "Failed to generate hashed token" # Create config echo "{\"write_tokens\": [\"${hashed_token}\"]}" > "${TEST_DIR}/config/sos_config.json" # Create test files echo "This is test file 1" > "${TEST_DIR}/test_files/test1.txt" echo "This is test file 2 with more content" > "${TEST_DIR}/test_files/test2.txt" dd if=/dev/urandom of="${TEST_DIR}/test_files/binary_test.bin" bs=1024 count=10 &>/dev/null cp "${SCRIPT_DIR}/sos" "${TEST_DIR}/test_files/sos_binary" log_info "Test environment ready" } # Start and configure test server start_test_server() { log_info "Starting SOS test server..." # Pull latest image docker pull "$IMAGE" &>/dev/null || log_warning "Using cached image" # Start container with dynamic port local container_id=$(docker run -d --name "${CONTAINER_NAME}" -p 80 "$IMAGE" 2>&1) [[ $? -ne 0 ]] && die "Failed to start container: ${container_id}" log_info "Container started: ${container_id:0:12}" # Get assigned port TEST_PORT=$(docker port "${CONTAINER_NAME}" 80 | cut -d: -f2) [[ -z "$TEST_PORT" ]] && die "Failed to get container port" log_info "Container port: ${TEST_PORT}" # Copy config and restart (for Docker-in-Docker compatibility) docker cp "${TEST_DIR}/config/sos_config.json" "${CONTAINER_NAME}:/data/sos_config.json" || die "Failed to copy config" docker exec "${CONTAINER_NAME}" mkdir -p /data/storage 2>/dev/null || true docker restart "${CONTAINER_NAME}" &>/dev/null || die "Failed to restart container" sleep 3 # Wait for restart # Update port if changed after restart local new_port=$(docker port "${CONTAINER_NAME}" 80 | cut -d: -f2) [[ -z "$new_port" ]] && die "Lost port mapping after restart" [[ "$new_port" != "$TEST_PORT" ]] && TEST_PORT="$new_port" && log_info "Port updated: ${TEST_PORT}" # Get network details for connectivity local container_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "${CONTAINER_NAME}") local host_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.Gateway}}{{end}}' "${CONTAINER_NAME}") # Wait for server readiness and determine connection method wait_for_server "$container_ip" "$host_ip" } # Wait for server and determine connection method wait_for_server() { local container_ip="${1:-}" local host_ip="${2:-}" log_info "Waiting for server readiness..." for attempt in {1..60}; do # Try all connection methods local container_code=$(curl -s --max-time 2 -o /dev/null -w "%{http_code}" "http://${container_ip}:80/" 2>/dev/null || echo "000") local host_code=$(curl -s --max-time 2 -o /dev/null -w "%{http_code}" "http://${host_ip}:${TEST_PORT}/" 2>/dev/null || echo "000") local local_code=$(curl -s --max-time 2 -o /dev/null -w "%{http_code}" "http://localhost:${TEST_PORT}/" 2>/dev/null || echo "000") # Debug first attempts if [[ $attempt -le 3 ]]; then log_info "Health check #${attempt}: container=${container_code}, host=${host_code}, localhost=${local_code}" fi # Check which method works if [[ "$container_code" =~ ^(200|204|404)$ ]]; then export SOS_TEST_HOST="${container_ip}:80" log_info "Server ready! Using container IP: ${SOS_TEST_HOST}" return 0 elif [[ "$host_code" =~ ^(200|204|404)$ ]]; then export SOS_TEST_HOST="${host_ip}:${TEST_PORT}" log_info "Server ready! Using host IP: ${SOS_TEST_HOST}" return 0 elif [[ "$local_code" =~ ^(200|204|404)$ ]]; then export SOS_TEST_HOST="localhost:${TEST_PORT}" log_info "Server ready! Using localhost: ${SOS_TEST_HOST}" return 0 fi # Progress indicator [[ $((attempt % 10)) -eq 0 ]] && log_info "Still waiting... (${attempt}/60)" sleep 1 done die "Server failed to become ready after 60 seconds" } # Execute SOS upload command run_sos_upload() { local host="${SOS_TEST_HOST:-localhost:${TEST_PORT}}" export SOS_WRITE_TOKEN="${TEST_TOKEN}" export SOS_TEST_MODE=1 "${SCRIPT_DIR}/sos" upload "$host" "$@" } # Test functions test_upload() { local file="$1" local label="$2" shift 2 log_info "Testing upload: ${file##*/} with label ${label}" if run_sos_upload "$file" "$label" "$@" 2>&1 | tee "${TEST_DIR}/upload_output.txt" | grep -q "Download URL:"; then log_info "Upload successful" return 0 else log_error "Upload failed" return 1 fi } test_retrieval() { local identifier="$1" local host="${SOS_TEST_HOST:-localhost:${TEST_PORT}}" log_info "Testing retrieval: ${identifier}" local response=$(curl -s "http://${host}/${identifier}") [[ -z "$response" ]] && log_error "Retrieval failed" && return 1 # For hash identifiers, also test metadata endpoint if [[ "$identifier" =~ ^[a-f0-9]{64}$ ]]; then curl -s "http://${host}/meta/${identifier}" | jq -e '.metadata' &>/dev/null || { log_error "Metadata retrieval failed" return 1 } fi log_info "Retrieval successful" return 0 } test_deduplication() { log_info "Testing deduplication..." # Create duplicate file cp "${TEST_DIR}/test_files/test1.txt" "${TEST_DIR}/test_files/test1_dup.txt" # Upload original and duplicate run_sos_upload "${TEST_DIR}/test_files/test1.txt" "dup:original" &>/dev/null local output=$(run_sos_upload "${TEST_DIR}/test_files/test1_dup.txt" "dup:copy" 2>&1) if echo "$output" | grep -q "File already exists, skipping upload"; then log_info "Deduplication working" return 0 else log_error "Deduplication failed" return 1 fi } test_metadata_update() { log_info "Testing metadata update..." run_sos_upload "${TEST_DIR}/test_files/test2.txt" "meta:v1" &>/dev/null local output=$(run_sos_upload "${TEST_DIR}/test_files/test2.txt" "meta:v2" "meta:updated" 2>&1) if echo "$output" | grep -q "File already exists"; then log_info "Metadata update working" return 0 else log_error "Metadata update failed" return 1 fi } test_api_endpoints() { log_info "Testing API endpoints..." local host="${SOS_TEST_HOST:-localhost:${TEST_PORT}}" # Test hash endpoint local hash=$(curl -s "http://${host}/hash/test:file1" | jq -r '.hash') [[ -z "$hash" || "$hash" == "null" ]] && log_error "Hash endpoint failed" && return 1 # Test exists endpoint local exists=$(curl -s "http://${host}/exists/${hash}" | jq -r '.exists') [[ "$exists" != "true" ]] && log_error "Exists endpoint failed" && return 1 log_info "API endpoints working" return 0 } test_invalid_auth() { log_info "Testing invalid authentication..." echo "auth test" > "${TEST_DIR}/test_files/auth_test.txt" export SOS_WRITE_TOKEN="invalid-token" export SOS_TEST_MODE=1 local host="${SOS_TEST_HOST:-localhost:${TEST_PORT}}" # Run upload with invalid token and properly capture exit code set +e "${SCRIPT_DIR}/sos" upload "$host" "${TEST_DIR}/test_files/auth_test.txt" "auth:test" >"${TEST_DIR}/auth_output.txt" 2>&1 local exit_code=$? set -e local output=$(cat "${TEST_DIR}/auth_output.txt") # The sos script should fail with non-zero exit code and show error message if [[ $exit_code -ne 0 ]] && echo "$output" | grep -qE "(FATAL:|Invalid write token|HTTP 403|HTTP 401)"; then log_info "Invalid auth correctly rejected (exit code: $exit_code)" return 0 else log_error "Invalid auth not rejected properly (exit code: $exit_code)" echo "Output:" && echo "$output" | head -5 return 1 fi } # Run all tests run_tests() { local tests=( "test_upload ${TEST_DIR}/test_files/test1.txt test:file1" "test_upload ${TEST_DIR}/test_files/test2.txt test:file2 version:1.0 env:test" "test_upload ${TEST_DIR}/test_files/binary_test.bin binary:test" "test_upload ${TEST_DIR}/test_files/sos_binary sos:test sos:latest" "test_retrieval test:file1" "test_deduplication" "test_metadata_update" "test_api_endpoints" "test_invalid_auth" ) local total=${#tests[@]} local passed=0 log_info "Running ${total} tests..." echo "" set +e # Don't exit on test failures for test in "${tests[@]}"; do eval "$test" && ((passed++)) || true echo "" done set -e # Summary echo "==================================" if [[ $passed -eq $total ]]; then echo -e "${GREEN}All tests passed!${NC} (${passed}/${total})" return 0 else echo -e "${RED}Some tests failed!${NC} (${passed}/${total})" return 1 fi } # Main execution main() { echo "SOS Test Suite" echo "==============" echo "" check_dependencies setup_test_environment start_test_server echo "" local result=0 run_tests || result=$? echo "" log_info "Test suite completed" [[ $result -ne 0 ]] && log_warning "Server logs:" && docker logs "${CONTAINER_NAME}" 2>&1 | tail -10 exit $result } main "$@"