diff --git a/.env.staging.example b/.env.staging.example new file mode 100644 index 0000000..2ff6edb --- /dev/null +++ b/.env.staging.example @@ -0,0 +1,34 @@ +# Staging Environment Configuration +ENVIRONMENT=staging + +# Database +DATABASE_URL=postgresql://postgres:staging_password@db_postgres:5432/staging_db +POSTGRES_DB=staging_db +POSTGRES_USER=postgres +POSTGRES_PASSWORD=staging_password_change_me + +# Application +SECRET_KEY=staging_secret_key_change_me_in_production +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=30 + +# External Services +REDIS_URL=redis://redis:6379/0 + +# Monitoring +GRAFANA_PASSWORD=staging_admin_password +PGADMIN_PASSWORD=staging_pgadmin_password + +# Notifications (optional) +SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK + +# Logging +LOG_LEVEL=INFO +LOG_FORMAT=json + +# CORS (для staging можно быть более либеральным) +CORS_ORIGINS=["http://localhost:3000", "https://staging.yourdomain.com"] + +# Rate Limiting +RATE_LIMIT_REQUESTS=100 +RATE_LIMIT_WINDOW=60 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 748d1ea..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug Report -about: Создать отчет об ошибке -title: '[BUG] ' -labels: bug -assignees: '' ---- - -## Описание ошибки -Краткое и понятное описание ошибки. - -## Шаги для воспроизведения -1. Перейти к '...' -2. Нажать на '...' -3. Прокрутить вниз до '...' -4. Увидеть ошибку - -## Ожидаемое поведение -Описание того, что должно было произойти. - -## Фактическое поведение -Описание того, что произошло на самом деле. - -## Скриншоты -Если применимо, добавьте скриншоты для объяснения проблемы. - -## Окружение -- ОС: [например, Ubuntu 20.04] -- Python версия: [например, 3.12] -- Версия приложения: [например, 1.0.0] - -## Логи -``` -Вставьте соответствующие логи здесь -``` - -## Дополнительный контекст -Добавьте любой другой контекст о проблеме здесь. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index c454305..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Feature Request -about: Предложить новую функциональность -title: '[FEATURE] ' -labels: enhancement -assignees: '' ---- - -## Описание функциональности -Краткое описание желаемой функциональности. - -## Мотивация -Объясните, зачем нужна эта функциональность. Какую проблему она решает? - -## Предлагаемое решение -Описание того, как вы видите реализацию этой функциональности. - -## Альтернативы -Описание альтернативных решений или функций, которые вы рассматривали. - -## Дополнительный контекст -Добавьте любой другой контекст или скриншоты о запросе функциональности здесь. - -## Приоритет -- [ ] Низкий -- [ ] Средний -- [ ] Высокий -- [ ] Критический \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a97e0d2..1d91fb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,6 @@ jobs: run: | uv sync --all-extras --dev uv pip install -r tests/requirements-test.txt - uv add ruff - name: Create test environment file run: | @@ -192,11 +191,81 @@ jobs: environment: staging steps: - - name: Deploy to staging + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Deploy to staging server + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.STAGING_HOST }} + username: ${{ secrets.STAGING_USER }} + key: ${{ secrets.STAGING_SSH_KEY }} + port: ${{ secrets.STAGING_PORT || 22 }} + script: | + # Переходим в директорию проекта + cd ${{ secrets.STAGING_PROJECT_PATH || '/opt/app' }} + + # Подтягиваем последние изменения + git fetch origin + git checkout develop + git pull origin develop + + # Экспортируем переменные окружения + export GITHUB_REPOSITORY=${{ github.repository }} + export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + export GITHUB_ACTOR=${{ github.actor }} + + # Запускаем деплой + ./scripts/deploy-staging.sh + + - name: Alternative local deployment (if no staging server) + if: ${{ !secrets.STAGING_HOST }} run: | - echo "Deploying to staging environment..." - # Здесь будет код для деплоя в staging - # Например, обновление Kubernetes deployment или Docker Compose + echo "No staging server configured, running local deployment simulation..." + + # Создаем .env.staging из примера + if [ ! -f ".env.staging" ]; then + cp .env.staging.example .env.staging + fi + + # Симулируем деплой + echo "Would deploy image: ghcr.io/${{ github.repository }}:develop" + echo "Staging deployment simulation completed" + + - name: Health check + if: ${{ secrets.STAGING_HOST }} + run: | + # Ждем запуска приложения + sleep 30 + + # Проверяем доступность + curl -f http://${{ secrets.STAGING_HOST }}:8081/health || echo "Health check failed" + + - name: Notify deployment status + if: always() + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + channel: '#deployments' + text: | + Staging Deployment ${{ job.status }} + Repository: ${{ github.repository }} + Branch: ${{ github.ref_name }} + Commit: ${{ github.sha }} + Actor: ${{ github.actor }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + continue-on-error: true deploy-production: name: Deploy to Production diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 4046751..3d1df06 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.12" + python-version: "3.13" - name: Install uv uses: astral-sh/setup-uv@v3 @@ -64,7 +64,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.12" + python-version: "3.13" - name: Install tools run: | @@ -80,4 +80,4 @@ jobs: - name: Check for complex functions run: | - xenon --max-absolute B --max-modules A --max-average A app/ \ No newline at end of file + xenon --max-absolute B --max-modules A --max-average A app/ diff --git a/app/auth/utils.py b/app/auth/utils.py index d70263f..25a2429 100644 --- a/app/auth/utils.py +++ b/app/auth/utils.py @@ -46,7 +46,7 @@ def set_tokens(response: Response, user_id: int): response.set_cookie( key="user_access_token", - value=access_token, + value=str(access_token or ""), httponly=True, secure=True, samesite="lax", @@ -54,7 +54,7 @@ def set_tokens(response: Response, user_id: int): response.set_cookie( key="user_refresh_token", - value=refresh_token, + value=str(refresh_token or ""), httponly=True, secure=True, samesite="lax", diff --git a/app/config.py b/app/config.py index ec51e62..aab9484 100644 --- a/app/config.py +++ b/app/config.py @@ -10,11 +10,11 @@ class Settings(BaseSettings): ) LOG_ROTATION: str = "10 MB" - SECRET_KEY: str = "test-secret-key" + SECRET_KEY: str = "test-secret-key" # noqa: S105 ALGORITHM: str = "HS256" POSTGRES_USER: str = "test" - POSTGRES_PASSWORD: str = "test" + POSTGRES_PASSWORD: str = "test" # noqa: S105 POSTGRES_HOST: str = "localhost" POSTGRES_PORT: int = 5432 POSTGRES_DB: str = "test" diff --git a/app/dao/base.py b/app/dao/base.py index 540edb7..2b650c4 100644 --- a/app/dao/base.py +++ b/app/dao/base.py @@ -16,7 +16,7 @@ class BaseDAO[T: Base]: - model: type[T] = None + model: type[T] def __init__(self, session: AsyncSession): self._session = session diff --git a/command b/command index 793dfc8..75a61a4 100644 --- a/command +++ b/command @@ -1,5 +1,6 @@ uvicorn app.main:app --host 0.0.0.0 --port 8080 docker compose up -d --build +docker compose -f docker-compose-prod.yml up -d --build docker compose down docker compose down -v diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml new file mode 100644 index 0000000..ad1e34f --- /dev/null +++ b/docker-compose-prod.yml @@ -0,0 +1,93 @@ +services: + app: + restart: always + image: ghcr.io/gooodh/fastapi_sqlalchemy_temp:latest + container_name: "app" + command: bash entrypoint.sh + ports: + - "8080:8080" + depends_on: + - db_postgres + + watchtower: + image: containrrr/watchtower + restart: always + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: + - --cleanup=true # Очистка старых неиспользуемых образов после обновления + - --interval=300 # Проверять обновления каждые 300 секунд (5 минут) + + + db_postgres: + image: postgres:15 + container_name: app_postgres_container + restart: always + env_file: + - .env + volumes: + - app_postgres:/var/lib/postgresql/data + ports: + - 5432:5432 + + pgadmin: + image: dpage/pgadmin4 + container_name: app_pgadmin_container + environment: + PGADMIN_DEFAULT_EMAIL: admin@pgadmin.com + PGADMIN_DEFAULT_PASSWORD: password + PGADMIN_LISTEN_PORT: 80 + ports: + - 15432:80 + volumes: + - app_pgadmin:/var/lib/pgadmin + depends_on: + - db_postgres + + loki: + image: grafana/loki:latest + container_name: "grafana_loki" + ports: + - "3100:3100" + volumes: + - ./loki-config.yml:/etc/loki/local-config.yml:ro + + grafana: + image: grafana/grafana:latest + container_name: "grafana" + ports: + - "3000:3000" + volumes: + - grafana_data:/var/lib/grafana + + promtail: + image: grafana/promtail:2.9.0 + container_name: promtail + volumes: + - /var/lib/docker/containers:/var/lib/docker/containers:ro + - /var/run/docker.sock:/var/run/docker.sock + - ./promtail-config.yaml:/etc/promtail/config.yml + command: -config.file=/etc/promtail/config.yml + privileged: true + depends_on: + - loki + + prometheus: + image: prom/prometheus:v2.52.0 + container_name: prometheus + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + - ./prometheus-data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.enable-lifecycle' + depends_on: + - loki + +volumes: + app_postgres: + app_pgadmin: + grafana_data: diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml new file mode 100644 index 0000000..c578223 --- /dev/null +++ b/docker-compose.staging.yml @@ -0,0 +1,110 @@ +services: + app: + restart: always + image: ghcr.io/${GITHUB_REPOSITORY}:develop + container_name: "app_staging" + command: bash entrypoint.sh + ports: + - "8081:8080" # Другой порт для staging + environment: + - ENVIRONMENT=staging + env_file: + - .env.staging + depends_on: + - db_postgres + labels: + - "com.centurylinklabs.watchtower.enable=true" + + watchtower: + image: containrrr/watchtower + restart: always + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /root/.docker/config.json:/config.json:ro + command: + - --cleanup=true + - --interval=300 + - --label-enable=true + - --rolling-restart=true + environment: + - WATCHTOWER_NOTIFICATIONS=slack + - WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=${SLACK_WEBHOOK_URL} + - WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=staging-watchtower + + db_postgres: + image: postgres:15 + container_name: app_postgres_staging_container + restart: always + environment: + - POSTGRES_DB=${POSTGRES_DB:-staging_db} + - POSTGRES_USER=${POSTGRES_USER:-postgres} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + volumes: + - app_postgres_staging:/var/lib/postgresql/data + ports: + - "5433:5432" # Другой порт для staging + + pgadmin: + image: dpage/pgadmin4 + container_name: app_pgadmin_staging_container + environment: + PGADMIN_DEFAULT_EMAIL: admin@staging.pgadmin.com + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-staging_password} + PGADMIN_LISTEN_PORT: 80 + ports: + - "15433:80" # Другой порт для staging + volumes: + - app_pgadmin_staging:/var/lib/pgadmin + depends_on: + - db_postgres + + # Мониторинг для staging (опционально) + loki: + image: grafana/loki:latest + container_name: "grafana_loki_staging" + ports: + - "3101:3100" + volumes: + - ./loki-config.yml:/etc/loki/local-config.yml:ro + + grafana: + image: grafana/grafana:latest + container_name: "grafana_staging" + ports: + - "3001:3000" + volumes: + - grafana_data_staging:/var/lib/grafana + environment: + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-staging_admin} + + promtail: + image: grafana/promtail:2.9.0 + container_name: promtail_staging + volumes: + - /var/lib/docker/containers:/var/lib/docker/containers:ro + - /var/run/docker.sock:/var/run/docker.sock + - ./promtail-config.yaml:/etc/promtail/config.yml + command: -config.file=/etc/promtail/config.yml + privileged: true + depends_on: + - loki + + prometheus: + image: prom/prometheus:v2.52.0 + container_name: prometheus_staging + ports: + - "9091:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + - ./prometheus-data-staging:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.enable-lifecycle' + depends_on: + - loki + +volumes: + app_postgres_staging: + app_pgadmin_staging: + grafana_data_staging: \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 1128a58..81d7279 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,7 @@ select = [ # Игнорируем некоторые правила ignore = [ + "S105", "E501", # line too long (handled by formatter) "B008", # do not perform function calls in argument defaults "S101", # use of assert detected (allow in tests) diff --git a/scripts/deploy-staging.sh b/scripts/deploy-staging.sh new file mode 100755 index 0000000..baba75a --- /dev/null +++ b/scripts/deploy-staging.sh @@ -0,0 +1,159 @@ +#!/bin/bash + +# Скрипт для деплоя в staging окружение +set -e + +echo "🚀 Starting staging deployment..." + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Функция для логирования +log() { + echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" +} + +warn() { + echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}" +} + +error() { + echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" + exit 1 +} + +# Проверка переменных окружения +check_env() { + log "Checking environment variables..." + + if [ -z "$GITHUB_REPOSITORY" ]; then + error "GITHUB_REPOSITORY environment variable is not set" + fi + + if [ -z "$GITHUB_TOKEN" ]; then + error "GITHUB_TOKEN environment variable is not set" + fi + + log "Environment variables OK" +} + +# Логин в GitHub Container Registry +login_registry() { + log "Logging into GitHub Container Registry..." + echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin + log "Successfully logged into registry" +} + +# Подтягивание последнего образа +pull_image() { + log "Pulling latest staging image..." + docker pull "ghcr.io/${GITHUB_REPOSITORY}:latest" || error "Failed to pull image" + log "Image pulled successfully" +} + +# Создание .env файла для staging +create_env_file() { + log "Creating staging environment file..." + + # Проверяем, существует ли .env.staging + if [ ! -f ".env.staging" ]; then + warn ".env.staging not found, creating from example..." + if [ -f ".env.staging.example" ]; then + cp .env.staging.example .env.staging + warn "Please update .env.staging with actual values before running in production" + else + error ".env.staging.example not found" + fi + fi + + log "Environment file ready" +} + +# Остановка старых контейнеров +stop_old_containers() { + log "Stopping old staging containers..." + docker-compose -f docker-compose.staging.yml down --remove-orphans || warn "No containers to stop" + log "Old containers stopped" +} + +# Запуск новых контейнеров +start_containers() { + log "Starting staging containers..." + + # Экспортируем переменные для docker-compose + export GITHUB_REPOSITORY + + docker-compose -f docker-compose.staging.yml up -d --remove-orphans + + if [ $? -eq 0 ]; then + log "Containers started successfully" + else + error "Failed to start containers" + fi +} + +# Проверка здоровья приложения +health_check() { + log "Performing health check..." + + # Ждем запуска приложения + sleep 30 + + # Проверяем доступность приложения + for i in {1..10}; do + if curl -f http://localhost:8081/health > /dev/null 2>&1; then + log "Health check passed" + return 0 + fi + warn "Health check attempt $i failed, retrying in 10 seconds..." + sleep 10 + done + + error "Health check failed after 10 attempts" +} + +# Очистка старых образов +cleanup() { + log "Cleaning up old images..." + docker image prune -f || warn "Failed to cleanup images" + log "Cleanup completed" +} + +# Отправка уведомления (опционально) +send_notification() { + if [ -n "$SLACK_WEBHOOK_URL" ]; then + log "Sending deployment notification..." + curl -X POST -H 'Content-type: application/json' \ + --data "{\"text\":\"🚀 Staging deployment completed successfully for ${GITHUB_REPOSITORY}\"}" \ + "$SLACK_WEBHOOK_URL" || warn "Failed to send notification" + fi +} + +# Основная функция +main() { + log "Starting staging deployment for ${GITHUB_REPOSITORY:-unknown}" + + check_env + login_registry + pull_image + create_env_file + stop_old_containers + start_containers + health_check + cleanup + send_notification + + log "🎉 Staging deployment completed successfully!" + log "Application is available at: http://localhost:8081" + log "PgAdmin is available at: http://localhost:15433" + log "Grafana is available at: http://localhost:3001" +} + +# Обработка ошибок +trap 'error "Deployment failed"' ERR + +# Запуск +main "$@" diff --git a/scripts/test-staging-local.sh b/scripts/test-staging-local.sh new file mode 100755 index 0000000..9ab108b --- /dev/null +++ b/scripts/test-staging-local.sh @@ -0,0 +1,223 @@ +#!/bin/bash + +# Скрипт для локального тестирования staging деплоя +set -e + +echo "🧪 Testing staging deployment locally..." + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { + echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" +} + +warn() { + echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}" +} + +error() { + echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" + exit 1 +} + +# Проверка Docker +check_docker() { + log "Checking Docker..." + if ! command -v docker &> /dev/null; then + error "Docker is not installed" + fi + + if ! docker info &> /dev/null; then + error "Docker is not running" + fi + + log "Docker is ready" +} + +# Сборка локального образа для тестирования +build_local_image() { + log "Building local staging image..." + + # Используем тег latest для локального тестирования + docker build -t "local/staging-test:latest" . + + if [ $? -eq 0 ]; then + log "Local image built successfully" + else + error "Failed to build local image" + fi +} + +# Создание тестового .env файла +create_test_env() { + log "Creating test environment file..." + + cat > .env.staging << EOF +# Test Staging Environment +ENVIRONMENT=staging +DATABASE_URL=postgresql://postgres:test_staging_password@db_postgres:5432/staging_db +POSTGRES_DB=staging_db +POSTGRES_USER=postgres +POSTGRES_PASSWORD=test_staging_password +SECRET_KEY=test_staging_secret_key_do_not_use_in_production +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=30 +LOG_LEVEL=DEBUG +CORS_ORIGINS=["http://localhost:3000", "http://localhost:8081"] +GRAFANA_PASSWORD=test_admin +PGADMIN_PASSWORD=test_pgadmin +EOF + + log "Test environment file created" +} + +# Модификация docker-compose для локального тестирования +create_test_compose() { + log "Creating test docker-compose file..." + + # Заменяем образ на локальный + sed 's|ghcr.io/${GITHUB_REPOSITORY}:latest|local/staging-test:latest|g' \ + docker-compose.staging.yml > docker-compose.staging.test.yml + + log "Test docker-compose file created" +} + +# Запуск тестового окружения +start_test_environment() { + log "Starting test staging environment..." + + # Останавливаем старые контейнеры + docker-compose -f docker-compose.staging.test.yml down --remove-orphans 2>/dev/null || true + + # Запускаем новые + docker-compose -f docker-compose.staging.test.yml up -d + + if [ $? -eq 0 ]; then + log "Test environment started successfully" + else + error "Failed to start test environment" + fi +} + +# Проверка здоровья +test_health() { + log "Testing application health..." + + # Ждем запуска + sleep 30 + + for i in {1..10}; do + if curl -f http://localhost:8081/health > /dev/null 2>&1; then + log "✅ Health check passed" + return 0 + fi + warn "Health check attempt $i failed, retrying in 5 seconds..." + sleep 5 + done + + error "❌ Health check failed" +} + +# Тестирование основных эндпоинтов +test_endpoints() { + log "Testing main endpoints..." + + # Тест корневого эндпоинта + if curl -f http://localhost:8081/ > /dev/null 2>&1; then + log "✅ Root endpoint OK" + else + warn "❌ Root endpoint failed" + fi + + # Тест документации + if curl -f http://localhost:8081/docs > /dev/null 2>&1; then + log "✅ Documentation endpoint OK" + else + warn "❌ Documentation endpoint failed" + fi + + # Тест метрик (если есть) + if curl -f http://localhost:8081/metrics > /dev/null 2>&1; then + log "✅ Metrics endpoint OK" + else + warn "❌ Metrics endpoint not available (this might be OK)" + fi +} + +# Показ логов +show_logs() { + log "Showing application logs..." + docker-compose -f docker-compose.staging.test.yml logs --tail=50 app +} + +# Показ статуса сервисов +show_status() { + log "Service status:" + docker-compose -f docker-compose.staging.test.yml ps + + echo "" + log "Available services:" + echo "🌐 Application: http://localhost:8081" + echo "🗄️ PgAdmin: http://localhost:15433 (admin@staging.pgadmin.com / test_pgadmin)" + echo "📊 Grafana: http://localhost:3001 (admin / test_admin)" + echo "🔍 Prometheus: http://localhost:9091" +} + +# Очистка +cleanup() { + log "Cleaning up test environment..." + docker-compose -f docker-compose.staging.test.yml down --remove-orphans + docker rmi local/staging-test:latest 2>/dev/null || true + rm -f .env.staging docker-compose.staging.test.yml + log "Cleanup completed" +} + +# Основная функция +main() { + case "${1:-test}" in + "test") + log "Running full staging test..." + check_docker + build_local_image + create_test_env + create_test_compose + start_test_environment + test_health + test_endpoints + show_status + log "🎉 Staging test completed successfully!" + ;; + "logs") + show_logs + ;; + "status") + show_status + ;; + "cleanup") + cleanup + ;; + "stop") + log "Stopping test environment..." + docker-compose -f docker-compose.staging.test.yml down + ;; + *) + echo "Usage: $0 {test|logs|status|cleanup|stop}" + echo " test - Run full staging test (default)" + echo " logs - Show application logs" + echo " status - Show service status" + echo " cleanup - Clean up test environment" + echo " stop - Stop test environment" + exit 1 + ;; + esac +} + +# Обработка прерывания +trap cleanup EXIT + +# Запуск +main "$@" diff --git a/tests/test_config.py b/tests/test_config.py index 6dc47d9..6af0ccc 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -33,7 +33,7 @@ def test_settings_default_values(self): == "{time:YYYY-MM-DD at HH:mm:ss} | {level} | {name}:{function}:{line} - {message}" ) assert settings.LOG_ROTATION == "10 MB" - assert settings.SECRET_KEY == "test_secret" + assert settings.SECRET_KEY == "test_secret" # noqa: S105 assert settings.ALGORITHM == "HS256" def test_database_url_generation(self): diff --git a/tests/test_performance.py b/tests/test_performance.py index 31b8757..0d0b86d 100644 --- a/tests/test_performance.py +++ b/tests/test_performance.py @@ -242,7 +242,7 @@ async def create_user_batch( successful = 0 for i in range(batch_size): # Добавляем случайность для уникальности - random_suffix = random.randint(1000, 9999) + random_suffix = random.randint(1000, 9999) # noqa: S311 user_id = batch_id * batch_size + i user_data = { "phone_number": f"+333{random_suffix}{user_id:04d}", diff --git a/tests/test_schemas.py b/tests/test_schemas.py index ce828b1..063cdc1 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -100,7 +100,7 @@ def test_user_register_valid(self): assert user.email == "test@example.com" assert user.first_name == "Тест" # Пароль должен быть захеширован - assert user.password != "password123" + assert user.password != "password123" # noqa: S105 assert len(user.password) > 20 # Хеш длиннее оригинального пароля def test_user_register_password_mismatch(self): @@ -137,7 +137,7 @@ def test_user_auth_valid(self): auth = SUserAuth(**auth_data) assert auth.email == "test@example.com" - assert auth.password == "password123" + assert auth.password == "password123" # noqa: S105 def test_role_add_model(self): """Тест схемы добавления роли.""" diff --git a/tests/test_utils.py b/tests/test_utils.py index aa0f901..a6b54d0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -21,7 +21,7 @@ class TestPasswordUtils: def test_get_password_hash(self): """Тест хеширования пароля.""" - password = "test_password" + password = "test_password" # noqa: S105 hashed = get_password_hash(password) assert hashed != password @@ -30,22 +30,22 @@ def test_get_password_hash(self): def test_verify_password_correct(self): """Тест проверки правильного пароля.""" - password = "test_password" + password = "test_password" # noqa: S105 hashed = get_password_hash(password) assert verify_password(password, hashed) is True def test_verify_password_incorrect(self): """Тест проверки неправильного пароля.""" - password = "test_password" - wrong_password = "wrong_password" + password = "test_password" # noqa: S105 + wrong_password = "wrong_password" # noqa: S105 hashed = get_password_hash(password) assert verify_password(wrong_password, hashed) is False def test_password_hash_uniqueness(self): """Тест уникальности хешей для одного пароля.""" - password = "test_password" + password = "test_password" # noqa: S105 hash1 = get_password_hash(password) hash2 = get_password_hash(password) @@ -64,7 +64,7 @@ class TestAuthenticateUser: async def test_authenticate_user_success(self): """Тест успешной аутентификации пользователя.""" # Создаем мок пользователя - password = "test_password" + password = "test_password" # noqa: S105 hashed_password = get_password_hash(password) user = Mock() @@ -78,8 +78,8 @@ async def test_authenticate_user_success(self): async def test_authenticate_user_wrong_password(self): """Тест аутентификации с неправильным паролем.""" # Создаем мок пользователя - password = "test_password" - wrong_password = "wrong_password" + password = "test_password" # noqa: S105 + wrong_password = "wrong_password" # noqa: S105 hashed_password = get_password_hash(password) user = Mock()