#!/usr/bin/env bash set -euo pipefail # Run tests against a disposable local MariaDB on host port 3307 using Docker Compose. # Requirements: Docker, docker compose plugin, Python, Alembic, pytest. # Usage: # chmod +x ./test_locally.sh # # From 7project/backend directory # ./test_locally.sh [--only-unit|--only-integration|--only-e2e] [pytest-args...] # # Examples: # ./test_locally.sh --only-unit -q # ./test_locally.sh --only-integration -k "login" # ./test_locally.sh --only-e2e -vv # # This script will: # 1) Start a MariaDB 11.4 container (ephemeral storage, port 3307) # 2) Wait until it's healthy # 3) Export env vars expected by the app (DATABASE_URL etc.) # 4) Run Alembic migrations # 5) Run pytest # 6) Tear everything down (containers and tmpfs data) COMPOSE_FILE="docker-compose.test.yml" SERVICE_NAME="mariadb" CONTAINER_NAME="test-mariadb" if ! command -v docker >/dev/null 2>&1; then echo "Docker is required but not found in PATH" >&2 exit 1 fi if ! docker compose version >/dev/null 2>&1; then echo "Docker Compose V2 plugin is required (docker compose)" >&2 exit 1 fi # Bring up the DB echo "Starting MariaDB (port 3307) with docker compose..." docker compose -f "$COMPOSE_FILE" up -d # Ensure we clean up on exit cleanup() { echo "\nTearing down docker compose stack..." docker compose -f "$COMPOSE_FILE" down -v || true } trap cleanup EXIT # Wait for healthy container echo -n "Waiting for MariaDB to become healthy" for i in {1..60}; do status=$(docker inspect -f '{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "") if [ "$status" = "healthy" ]; then echo " -> healthy" break fi echo -n "." sleep 1 if [ $i -eq 60 ]; then echo "\nMariaDB did not become healthy in time" >&2 exit 1 fi done # Export env vars for the app/tests (match app/core/db.py expectations) export MARIADB_HOST=127.0.0.1 export MARIADB_PORT=3307 export MARIADB_DB=group_project export MARIADB_USER=appuser export MARIADB_PASSWORD=apppass export DATABASE_URL="mysql+asyncmy://$MARIADB_USER:$MARIADB_PASSWORD@$MARIADB_HOST:$MARIADB_PORT/$MARIADB_DB" export PYTEST_RUN_CONFIG="True" # Determine which tests to run based on flags UNIT_TESTS="tests/test_unit_user_service.py" INTEGRATION_TESTS="tests/test_integration_app.py" E2E_TESTS="tests/test_e2e.py" FLAG_COUNT=0 TEST_TARGET="" declare -a PYTEST_ARGS=() for arg in "$@"; do case "$arg" in --only-unit) TEST_TARGET="$UNIT_TESTS"; FLAG_COUNT=$((FLAG_COUNT+1));; --only-integration) TEST_TARGET="$INTEGRATION_TESTS"; FLAG_COUNT=$((FLAG_COUNT+1));; --only-e2e) TEST_TARGET="$E2E_TESTS"; FLAG_COUNT=$((FLAG_COUNT+1));; *) PYTEST_ARGS+=("$arg");; esac done if [ "$FLAG_COUNT" -gt 1 ]; then echo "Error: Use only one of --only-unit, --only-integration, or --only-e2e" >&2 exit 2 fi # Run Alembic migrations then tests pushd . >/dev/null echo "Running Alembic migrations..." alembic upgrade head echo "Running pytest..." if [ -n "$TEST_TARGET" ]; then # Use "${PYTEST_ARGS[@]:-}" to safely expand empty array with 'set -u' pytest "$TEST_TARGET" "${PYTEST_ARGS[@]:-}" else # Use "${PYTEST_ARGS[@]:-}" to safely expand empty array with 'set -u' pytest "${PYTEST_ARGS[@]:-}" fi popd >/dev/null # Cleanup handled by trap