axonhub / scripts /migration /migration-test.sh
llzai's picture
Upload 1793 files
9853396 verified
#!/bin/bash
# AxonHub Migration Test Script
# Tests database migration from a specified tag to current branch
# Usage: ./migration-test.sh <from-tag> [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 <<EOF
AxonHub Migration Test Script
Usage:
./migration-test.sh <from-tag> [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 <<EOF
{
"ownerEmail": "${INIT_OWNER_EMAIL}",
"ownerPassword": "${INIT_OWNER_PASSWORD}",
"ownerFirstName": "${INIT_OWNER_FIRST_NAME}",
"ownerLastName": "${INIT_OWNER_LAST_NAME}",
"brandName": "${INIT_BRAND_NAME}"
}
EOF
)
print_info "Initializing system via API (${url})..." >&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" <<EOF
{
"from_tag": "$from_tag",
"from_version": "$old_version",
"to_version": "$current_version",
"platform": "$platform",
"steps": [
{
"step": 1,
"action": "initialize",
"version": "$from_tag",
"binary": "$old_binary",
"description": "Initialize database with version $old_version"
},
{
"step": 2,
"action": "migrate",
"version": "current",
"binary": "$current_binary",
"description": "Migrate database to version $current_version"
}
]
}
EOF
print_success "Migration plan generated: $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 "$@"