#!/bin/bash # AxonHub Migration Test Script # Tests database migration from a specified tag to current branch # Usage: ./migration-test.sh [options] set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" CACHE_DIR="${SCRIPT_DIR}/migration-test/cache" WORK_DIR="${SCRIPT_DIR}/migration-test/work" DB_FILE="${WORK_DIR}/migration-test.db" LOG_FILE="${WORK_DIR}/migration-test.log" PLAN_FILE="${WORK_DIR}/migration-plan.json" # E2E configuration (keep consistent with e2e-test.sh) E2E_PORT=8099 # Database configuration DB_TYPE="sqlite" # Default: sqlite, mysql, postgres MYSQL_CONTAINER="axonhub-migration-mysql" MYSQL_PORT=13306 MYSQL_ROOT_PASSWORD="axonhub_test_root" MYSQL_DATABASE="axonhub_e2e" MYSQL_USER="axonhub" MYSQL_PASSWORD="axonhub_test" POSTGRES_CONTAINER="axonhub-migration-postgres" POSTGRES_PORT=15432 POSTGRES_DATABASE="axonhub_e2e" POSTGRES_USER="axonhub" POSTGRES_PASSWORD="axonhub_test" # System initialization defaults (override via AXONHUB_INIT_* env vars) INIT_OWNER_EMAIL="${AXONHUB_INIT_OWNER_EMAIL:-owner@example.com}" INIT_OWNER_PASSWORD="${AXONHUB_INIT_OWNER_PASSWORD:-InitPassword123!}" INIT_OWNER_FIRST_NAME="${AXONHUB_INIT_OWNER_FIRST_NAME:-System}" INIT_OWNER_LAST_NAME="${AXONHUB_INIT_OWNER_LAST_NAME:-Owner}" INIT_BRAND_NAME="${AXONHUB_INIT_BRAND_NAME:-AxonHub Migration Test}" # GitHub repository REPO="looplj/axonhub" GITHUB_API="https://api.github.com/repos/${REPO}" print_info() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } print_step() { echo "" echo -e "${GREEN}==>${NC} $1" } usage() { cat < [options] Arguments: from-tag Git tag to test migration from (e.g., v0.1.0) Options: --db-type TYPE Database type: sqlite, mysql, postgres (default: sqlite) --skip-download Skip downloading binary if cached version exists --skip-e2e Skip running e2e tests after migration --skip-init-system Skip system initialization step (reuse existing database state) --keep-artifacts Keep work directory after test completion --keep-db Keep database container after test completion -h, --help Show this help and exit Examples: ./migration-test.sh v0.1.0 ./migration-test.sh v0.1.0 --db-type mysql ./migration-test.sh v0.1.0 --db-type postgres --skip-e2e ./migration-test.sh v0.2.0 --keep-artifacts Description: This script tests database migration by: 1. Setting up database (SQLite file or Docker container for MySQL/PostgreSQL) 2. Downloading the binary for the specified tag from GitHub releases 3. Initializing a database with the old version 4. Running migration to the current branch version 5. Executing e2e tests to verify the migration Supported databases: - SQLite (default, no Docker required) - MySQL (requires Docker, creates temporary container) - PostgreSQL (requires Docker, creates temporary container) Binaries are cached in: ${CACHE_DIR} Test artifacts are in: ${WORK_DIR} EOF } check_docker() { if ! command -v docker >/dev/null 2>&1; then print_error "Docker is not installed. Please install Docker to use MySQL or PostgreSQL." exit 1 fi if ! docker info >/dev/null 2>&1; then print_error "Docker daemon is not running. Please start Docker." exit 1 fi } setup_mysql() { print_step "Setting up MySQL database" >&2 check_docker # Stop and remove existing container if exists if docker ps -a --format '{{.Names}}' | grep -q "^${MYSQL_CONTAINER}$"; then print_info "Removing existing MySQL container..." >&2 docker rm -f "$MYSQL_CONTAINER" >/dev/null 2>&1 || true fi # Start MySQL container print_info "Starting MySQL container..." >&2 docker run -d \ --name "$MYSQL_CONTAINER" \ -e MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASSWORD" \ -e MYSQL_DATABASE="$MYSQL_DATABASE" \ -e MYSQL_USER="$MYSQL_USER" \ -e MYSQL_PASSWORD="$MYSQL_PASSWORD" \ -p "${MYSQL_PORT}:3306" \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci \ >/dev/null # Wait for MySQL to be ready print_info "Waiting for MySQL to be ready..." >&2 for i in {1..30}; do if docker exec "$MYSQL_CONTAINER" mysqladmin ping -h localhost -u root -p"$MYSQL_ROOT_PASSWORD" >/dev/null 2>&1; then print_success "MySQL is ready" >&2 return 0 fi sleep 1 done print_error "MySQL failed to start" >&2 docker logs "$MYSQL_CONTAINER" >&2 exit 1 } setup_postgres() { print_step "Setting up PostgreSQL database" >&2 check_docker # Stop and remove existing container if exists if docker ps -a --format '{{.Names}}' | grep -q "^${POSTGRES_CONTAINER}$"; then print_info "Removing existing PostgreSQL container..." >&2 docker rm -f "$POSTGRES_CONTAINER" >/dev/null 2>&1 || true fi # Start PostgreSQL container print_info "Starting PostgreSQL container..." >&2 docker run -d \ --name "$POSTGRES_CONTAINER" \ -e POSTGRES_DB="$POSTGRES_DATABASE" \ -e POSTGRES_USER="$POSTGRES_USER" \ -e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \ -p "${POSTGRES_PORT}:5432" \ postgres:15-alpine \ >/dev/null # Wait for PostgreSQL to be ready print_info "Waiting for PostgreSQL to be ready..." >&2 for i in {1..30}; do if docker exec "$POSTGRES_CONTAINER" pg_isready -U "$POSTGRES_USER" >/dev/null 2>&1; then print_success "PostgreSQL is ready" >&2 return 0 fi sleep 1 done print_error "PostgreSQL failed to start" >&2 docker logs "$POSTGRES_CONTAINER" >&2 exit 1 } cleanup_database() { if [[ "$KEEP_DB" == "true" ]]; then print_info "Keeping database container (--keep-db specified)" >&2 return fi case "$DB_TYPE" in mysql) if docker ps -a --format '{{.Names}}' | grep -q "^${MYSQL_CONTAINER}$"; then print_info "Removing MySQL container..." >&2 docker rm -f "$MYSQL_CONTAINER" >/dev/null 2>&1 || true fi ;; postgres) if docker ps -a --format '{{.Names}}' | grep -q "^${POSTGRES_CONTAINER}$"; then print_info "Removing PostgreSQL container..." >&2 docker rm -f "$POSTGRES_CONTAINER" >/dev/null 2>&1 || true fi ;; sqlite) # SQLite cleanup handled by cleanup() function ;; esac } get_db_dsn() { case "$DB_TYPE" in sqlite) echo "file:${DB_FILE}?cache=shared&_fk=1" ;; mysql) echo "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(localhost:${MYSQL_PORT})/${MYSQL_DATABASE}?charset=utf8mb4&parseTime=True&loc=Local" ;; postgres) echo "host=localhost port=${POSTGRES_PORT} user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=${POSTGRES_DATABASE} sslmode=disable" ;; *) print_error "Unknown database type: $DB_TYPE" >&2 exit 1 ;; esac } get_db_dialect() { case "$DB_TYPE" in sqlite) echo "sqlite3" ;; mysql) echo "mysql" ;; postgres) echo "postgres" ;; *) print_error "Unknown database type: $DB_TYPE" >&2 exit 1 ;; esac } detect_architecture() { local arch=$(uname -m) local os=$(uname -s | tr '[:upper:]' '[:lower:]') case $arch in x86_64|amd64) arch="amd64" ;; aarch64|arm64) arch="arm64" ;; *) print_error "Unsupported architecture: $arch" exit 1 ;; esac case $os in linux) os="linux" ;; darwin) os="darwin" ;; *) print_error "Unsupported operating system: $os" exit 1 ;; esac echo "${os}_${arch}" } curl_gh() { local url="$1" local headers=( -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" -H "User-Agent: axonhub-migration-test" ) if [[ -n "$GITHUB_TOKEN" ]]; then headers+=( -H "Authorization: Bearer $GITHUB_TOKEN" ) fi curl -fsSL "${headers[@]}" "$url" } get_asset_download_url() { local version=$1 local platform=$2 local url="" print_info "Resolving asset download URL for ${version} (${platform})..." >&2 if json=$(curl_gh "${GITHUB_API}/releases/tags/${version}" 2>/dev/null); then if command -v jq >/dev/null 2>&1; then url=$(echo "$json" | jq -r --arg platform "$platform" \ '.assets[]?.browser_download_url | select(test($platform)) | select(endswith(".zip"))' | head -n1) else url=$(echo "$json" \ | tr -d '\n\r\t' \ | sed -nE 's/.*"browser_download_url"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' \ | grep "$platform" \ | grep '\.zip$' -m 1) fi fi # Fallback to patterned URL if [[ -z "$url" ]]; then print_warning "API failed, trying patterned URL..." >&2 local clean_version="${version#v}" local filename="axonhub_${clean_version}_${platform}.zip" local candidate="https://github.com/${REPO}/releases/download/${version}/${filename}" if curl -fsI "$candidate" >/dev/null 2>&1; then url="$candidate" fi fi if [[ -z "$url" ]]; then print_error "Could not find asset for platform ${platform} in release ${version}" >&2 exit 1 fi echo "$url" } download_binary() { local version=$1 local platform=$2 local cache_path="${CACHE_DIR}/${version}/axonhub" # Check if cached if [[ -f "$cache_path" && "$SKIP_DOWNLOAD" == "true" ]]; then print_info "Using cached binary: $cache_path" >&2 echo "$cache_path" return fi # Create cache directory mkdir -p "${CACHE_DIR}/${version}" # Download if not cached if [[ ! -f "$cache_path" ]]; then print_info "Downloading AxonHub ${version} for ${platform}..." >&2 local download_url download_url=$(get_asset_download_url "$version" "$platform") local filename=$(basename "$download_url") local temp_dir=$(mktemp -d) if ! curl -fSL -o "${temp_dir}/${filename}" "$download_url"; then print_error "Failed to download AxonHub asset" >&2 rm -rf "$temp_dir" exit 1 fi print_info "Extracting archive..." >&2 if ! command -v unzip >/dev/null 2>&1; then print_error "unzip command not found. Please install unzip." >&2 rm -rf "$temp_dir" exit 1 fi if ! unzip -q "${temp_dir}/${filename}" -d "$temp_dir"; then print_error "Failed to extract archive" >&2 rm -rf "$temp_dir" exit 1 fi # Find and copy binary local binary_path binary_path=$(find "$temp_dir" -name "axonhub" -type f | head -1) if [[ -z "$binary_path" ]]; then print_error "Could not find axonhub binary in archive" >&2 rm -rf "$temp_dir" exit 1 fi cp "$binary_path" "$cache_path" chmod +x "$cache_path" rm -rf "$temp_dir" print_success "Binary cached: $cache_path" >&2 else print_info "Using cached binary: $cache_path" >&2 fi echo "$cache_path" } build_current_binary() { local binary_path="${WORK_DIR}/axonhub-current" print_info "Building current branch binary..." >&2 cd "$PROJECT_ROOT" if ! go build -o "$binary_path" ./cmd/axonhub; then print_error "Failed to build current branch binary" >&2 exit 1 fi chmod +x "$binary_path" print_success "Current binary built: $binary_path" >&2 echo "$binary_path" } get_binary_version() { local binary_path=$1 local version if version=$("$binary_path" version 2>/dev/null | head -n1 | tr -d '\r'); then echo "$version" else echo "unknown" fi } initialize_database() { local binary_path=$1 local version=$2 print_info "Initializing database with version ${version}..." >&2 print_info "Binary path: $binary_path" >&2 print_info "Database type: $DB_TYPE" >&2 # Remove old SQLite database file if using SQLite if [[ "$DB_TYPE" == "sqlite" ]]; then print_info "Removing old SQLite database file: $DB_FILE" >&2 rm -f "$DB_FILE" fi local db_dsn db_dsn=$(get_db_dsn) print_info "Database DSN: $db_dsn" >&2 local db_dialect db_dialect=$(get_db_dialect) # Start server to initialize database print_info "Starting server for initialization (PID will be captured)..." >&2 AXONHUB_SERVER_PORT=$E2E_PORT \ AXONHUB_DB_DIALECT="$db_dialect" \ AXONHUB_DB_DSN="$db_dsn" \ AXONHUB_LOG_OUTPUT="file" \ AXONHUB_LOG_FILE_PATH="$LOG_FILE" \ AXONHUB_LOG_LEVEL="info" \ "$binary_path" > /dev/null 2>&1 & local pid=$! print_info "Server started with PID: $pid" >&2 local init_status=0 if ! wait_for_server_ready "$E2E_PORT" 60; then print_error "Server failed to become ready" >&2 kill "$pid" 2>/dev/null || true wait "$pid" 2>/dev/null || true print_error "Failed to start server for database initialization" >&2 exit 1 fi if [[ "$SKIP_INIT_SYSTEM" == "true" ]]; then print_warning "Skipping system initialization (--skip-init-system specified)" >&2 else if ! initialize_system_via_api; then init_status=1 fi fi print_info "Stopping server (PID: $pid)..." >&2 kill "$pid" 2>/dev/null || true wait "$pid" 2>/dev/null || true if [[ $init_status -ne 0 ]]; then print_error "Database initialization failed" >&2 exit 1 fi if [[ "$SKIP_INIT_SYSTEM" == "true" ]]; then print_success "Database prepared with existing state (initialization skipped)" >&2 else print_success "Database initialized with version ${version}" >&2 fi } run_migration() { local binary_path=$1 local version=$2 print_info "Running migration with version ${version}..." >&2 print_info "Binary path: $binary_path" >&2 print_info "Database type: $DB_TYPE" >&2 local db_dsn db_dsn=$(get_db_dsn) print_info "Database DSN: $db_dsn" >&2 local db_dialect db_dialect=$(get_db_dialect) # Run migration by starting and stopping the server print_info "Starting server for migration (PID will be captured)..." >&2 AXONHUB_SERVER_PORT=$E2E_PORT \ AXONHUB_DB_DIALECT="$db_dialect" \ AXONHUB_DB_DSN="$db_dsn" \ AXONHUB_LOG_OUTPUT="file" \ AXONHUB_LOG_FILE_PATH="$LOG_FILE" \ AXONHUB_LOG_LEVEL="debug" \ "$binary_path" > /dev/null 2>&1 & local pid=$! print_info "Server started with PID: $pid" >&2 # Wait for server to be ready print_info "Waiting for migration to complete..." >&2 if wait_for_server_ready "$E2E_PORT" 60; then # Check if the server process is still running after becoming ready if ! kill -0 "$pid" 2>/dev/null; then # Server process has already exited, check its exit status local exit_status=0 wait "$pid" 2>/dev/null || exit_status=$? print_error "Server process exited unexpectedly with status: $exit_status" >&2 print_error "Check log file for details: $LOG_FILE" >&2 exit 1 fi # Give the server a bit more time to ensure migration completes and server stays stable print_info "Server is ready, verifying stability..." >&2 sleep 3 # Check again if server is still running if ! kill -0 "$pid" 2>/dev/null; then local exit_status=0 wait "$pid" 2>/dev/null || exit_status=$? print_error "Server process crashed after startup with status: $exit_status" >&2 print_error "Check log file for details: $LOG_FILE" >&2 exit 1 fi print_success "Migration completed successfully" >&2 print_info "Stopping server (PID: $pid)..." >&2 kill "$pid" 2>/dev/null || true wait "$pid" 2>/dev/null || true return 0 fi print_error "Server failed to become ready, stopping..." >&2 kill "$pid" 2>/dev/null || true wait "$pid" 2>/dev/null || true print_error "Migration failed or timed out" >&2 exit 1 } wait_for_server_ready() { local port=$1 local max_attempts=${2:-60} print_info "Waiting for server to be ready on port ${port}..." >&2 for ((attempt = 1; attempt <= max_attempts; attempt++)); do if curl -s "http://localhost:${port}/health" > /dev/null 2>&1 || \ curl -s "http://localhost:${port}/" > /dev/null 2>&1; then return 0 fi sleep 1 done return 1 } initialize_system_via_api() { local url="http://localhost:${E2E_PORT}/admin/system/initialize" local response_file="${WORK_DIR}/initialize-response.json" local payload payload=$(cat <&2 for attempt in {1..5}; do local tmp_file="${response_file}.tmp" local status status=$(curl -sS -o "$tmp_file" -w "%{http_code}" \ -H "Content-Type: application/json" \ -X POST \ -d "$payload" \ "$url" || echo "000") status=$(echo "$status" | tr -d '\n\r') if [[ -f "$tmp_file" ]]; then mv "$tmp_file" "$response_file" fi case "$status" in 200) print_success "System initialized successfully via API" >&2 return 0 ;; 400) if [[ -f "$response_file" ]] && grep -qi "already initialized" "$response_file"; then print_warning "System initialization API returned already initialized" >&2 return 0 fi ;; esac if [[ "$status" == "000" ]]; then print_warning "Initialization attempt ${attempt} failed: unable to reach server" >&2 else local body="" if [[ -f "$response_file" ]]; then body=$(cat "$response_file") fi print_warning "Initialization attempt ${attempt} failed (status: ${status}): ${body}" >&2 fi sleep 2 done print_error "System initialization via API failed after multiple attempts" >&2 if [[ -f "$response_file" ]]; then print_error "Last response: $(cat "$response_file")" >&2 fi return 1 } generate_migration_plan() { local from_tag=$1 local platform=$2 print_info "Generating migration plan..." >&2 # For now, we'll create a simple two-step plan: # 1. Initialize with old version # 2. Migrate to current version local old_binary old_binary=$(download_binary "$from_tag" "$platform") local current_binary current_binary=$(build_current_binary) local old_version old_version=$(get_binary_version "$old_binary") local current_version current_version=$(get_binary_version "$current_binary") # Create plan JSON cat > "$PLAN_FILE" <&2 # Display plan echo "" >&2 echo "Migration Plan:" >&2 echo " From: $from_tag ($old_version)" >&2 echo " To: current ($current_version)" >&2 echo " Steps:" >&2 echo " 1. Initialize database with $from_tag" >&2 echo " 2. Migrate to current branch" >&2 echo "" >&2 } execute_migration_plan() { print_step "Executing migration plan" >&2 if [[ ! -f "$PLAN_FILE" ]]; then print_error "Migration plan not found: $PLAN_FILE" >&2 exit 1 fi # Parse plan and execute local from_tag from_version to_version if command -v jq >/dev/null 2>&1; then from_tag=$(jq -r '.from_tag' "$PLAN_FILE") from_version=$(jq -r '.from_version' "$PLAN_FILE") to_version=$(jq -r '.to_version' "$PLAN_FILE") # Execute step 1: Initialize local step1_binary step1_binary=$(jq -r '.steps[0].binary' "$PLAN_FILE") if [[ "$SKIP_INIT_SYSTEM" == "true" ]]; then print_step "Step 2.1: Initialize database with $from_tag ($from_version) [skipped]" >&2 print_warning "System initialization skipped by option" >&2 else print_step "Step 2.1: Initialize database with $from_tag ($from_version)" >&2 print_info "Using binary: $step1_binary" >&2 initialize_database "$step1_binary" "$from_version" print_success "Step 2.1 completed" >&2 fi # Execute step 2: Migrate local step2_binary step2_binary=$(jq -r '.steps[1].binary' "$PLAN_FILE") print_step "Step 2.2: Migrate to current ($to_version)" >&2 print_info "Using binary: $step2_binary" >&2 run_migration "$step2_binary" "$to_version" print_success "Step 2.2 completed" >&2 else # Fallback without jq from_tag=$(grep '"from_tag"' "$PLAN_FILE" | sed -E 's/.*"from_tag"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') from_version=$(grep '"from_version"' "$PLAN_FILE" | sed -E 's/.*"from_version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') to_version=$(grep '"to_version"' "$PLAN_FILE" | sed -E 's/.*"to_version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') # Get binaries from plan local step1_binary=$(grep -A 5 '"step": 1' "$PLAN_FILE" | grep '"binary"' | sed -E 's/.*"binary"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') local step2_binary=$(grep -A 5 '"step": 2' "$PLAN_FILE" | grep '"binary"' | sed -E 's/.*"binary"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') if [[ "$SKIP_INIT_SYSTEM" == "true" ]]; then print_step "Step 2.1: Initialize database with $from_tag ($from_version) [skipped]" >&2 print_warning "System initialization skipped by option" >&2 else print_step "Step 2.1: Initialize database with $from_tag ($from_version)" >&2 print_info "Using binary: $step1_binary" >&2 initialize_database "$step1_binary" "$from_version" print_success "Step 2.1 completed" >&2 fi print_step "Step 2.2: Migrate to current ($to_version)" >&2 print_info "Using binary: $step2_binary" >&2 run_migration "$step2_binary" "$to_version" print_success "Step 2.2 completed" >&2 fi print_success "Migration plan executed successfully" >&2 } run_e2e_tests() { print_step "Running e2e tests to verify migration" >&2 local db_dsn db_dsn=$(get_db_dsn) local db_dialect db_dialect=$(get_db_dialect) if [[ "$DB_TYPE" == "sqlite" ]]; then local e2e_db="${SCRIPT_DIR}/../e2e/axonhub-e2e.db" cp "$DB_FILE" "$e2e_db" print_info "Database copied to e2e location: $e2e_db" >&2 cd "$PROJECT_ROOT" if env \ AXONHUB_E2E_DB_TYPE="$DB_TYPE" \ AXONHUB_E2E_DB_DIALECT="$db_dialect" \ ./scripts/e2e/e2e-test.sh; then print_success "E2E tests passed!" >&2 return 0 else print_error "E2E tests failed" >&2 return 1 fi fi print_info "Reusing migrated $DB_TYPE database for e2e tests" >&2 cd "$PROJECT_ROOT" if env \ AXONHUB_E2E_DB_TYPE="$DB_TYPE" \ AXONHUB_E2E_DB_DIALECT="$db_dialect" \ AXONHUB_E2E_DB_DSN="$db_dsn" \ AXONHUB_E2E_USE_EXISTING_DB="true" \ ./scripts/e2e/e2e-test.sh; then print_success "E2E tests passed!" >&2 return 0 else print_error "E2E tests failed" >&2 return 1 fi } cleanup() { # Cleanup database containers cleanup_database # Cleanup work directory if [[ "$KEEP_ARTIFACTS" != "true" ]]; then print_info "Cleaning up work directory..." >&2 rm -rf "$WORK_DIR" else print_info "Keeping artifacts in: $WORK_DIR" >&2 fi } main() { print_info "AxonHub Migration Test Script" >&2 echo "" >&2 # Parse arguments local from_tag="" SKIP_DOWNLOAD="false" SKIP_E2E="false" SKIP_INIT_SYSTEM="false" KEEP_ARTIFACTS="false" KEEP_DB="false" while [[ $# -gt 0 ]]; do case "$1" in --db-type) if [[ -z "$2" || "$2" == -* ]]; then print_error "--db-type requires an argument" >&2 usage exit 1 fi DB_TYPE="$2" shift 2 ;; --skip-download) SKIP_DOWNLOAD="true" shift ;; --skip-e2e) SKIP_E2E="true" shift ;; --skip-init-system) SKIP_INIT_SYSTEM="true" shift ;; --keep-artifacts) KEEP_ARTIFACTS="true" shift ;; --keep-db) KEEP_DB="true" shift ;; -h|--help) usage exit 0 ;; -*) print_error "Unknown option: $1" >&2 usage exit 1 ;; *) if [[ -z "$from_tag" ]]; then from_tag="$1" shift else print_error "Too many arguments" >&2 usage exit 1 fi ;; esac done if [[ -z "$from_tag" ]]; then print_error "Missing required argument: from-tag" >&2 usage exit 1 fi # Validate database type case "$DB_TYPE" in sqlite|mysql|postgres) ;; *) print_error "Invalid database type: $DB_TYPE (must be sqlite, mysql, or postgres)" >&2 exit 1 ;; esac print_info "Testing migration from $from_tag to current branch" >&2 print_info "Database type: $DB_TYPE" >&2 echo "" >&2 # Detect platform local platform platform=$(detect_architecture) print_info "Detected platform: $platform" >&2 # Setup directories mkdir -p "$CACHE_DIR" "$WORK_DIR" # Setup database case "$DB_TYPE" in mysql) setup_mysql ;; postgres) setup_postgres ;; sqlite) print_info "Using SQLite database: $DB_FILE" >&2 ;; esac # Generate migration plan print_step "Step 1: Generate migration plan" >&2 generate_migration_plan "$from_tag" "$platform" # Execute migration plan print_step "Step 2: Execute migration plan" >&2 execute_migration_plan # Run e2e tests if [[ "$SKIP_E2E" != "true" ]]; then print_step "Step 3: Run e2e tests" >&2 if ! run_e2e_tests; then cleanup exit 1 fi else print_warning "Skipping e2e tests (--skip-e2e specified)" >&2 fi # Cleanup cleanup echo "" >&2 print_success "Migration test completed successfully!" >&2 echo "" >&2 print_info "Summary:" >&2 echo " From: $from_tag" >&2 echo " To: current branch" >&2 echo " Database Type: $DB_TYPE" >&2 case "$DB_TYPE" in sqlite) echo " Database File: $DB_FILE" >&2 ;; mysql) echo " MySQL Container: $MYSQL_CONTAINER" >&2 echo " MySQL Port: $MYSQL_PORT" >&2 echo " MySQL Database: $MYSQL_DATABASE" >&2 if [[ "$KEEP_DB" == "true" ]]; then echo " MySQL DSN: ${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(localhost:${MYSQL_PORT})/${MYSQL_DATABASE}" >&2 fi ;; postgres) echo " PostgreSQL Container: $POSTGRES_CONTAINER" >&2 echo " PostgreSQL Port: $POSTGRES_PORT" >&2 echo " PostgreSQL Database: $POSTGRES_DATABASE" >&2 if [[ "$KEEP_DB" == "true" ]]; then echo " PostgreSQL DSN: host=localhost port=${POSTGRES_PORT} user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=${POSTGRES_DATABASE}" >&2 fi ;; esac echo " Log: $LOG_FILE" >&2 echo " Cache: $CACHE_DIR" >&2 echo "" >&2 } # Run main function main "$@"