"""Ротация пароля администратора из .env (#73): `python -m app.bootstrap --reset-admin-password`.""" from __future__ import annotations import pytest from fastapi.testclient import TestClient from sqlmodel import Session, select from app import bootstrap from app.core.security import verify_password from app.models import User from tests.conftest import csrf_headers OLD, NEW = "secret123", "brand-new-admin-pw" def _admin(engine) -> User: with Session(engine) as s: return s.exec(select(User).where(User.role == "admin")).one() def _login(client: TestClient, password: str): return client.post( "/api/admin/auth/login", json={"username": "admin", "password": password}, headers=csrf_headers(client), ) def test_reset_applies_env_password_and_revokes_sessions( client: TestClient, engine, make_admin, monkeypatch ): make_admin("admin", OLD) assert _login(client, OLD).status_code == 200 assert client.get("/api/admin/me").status_code == 200 version = _admin(engine).token_version monkeypatch.setattr(bootstrap.settings, "admin_password", NEW) with Session(engine) as s: bootstrap.reset_admin_password(s) admin = _admin(engine) assert verify_password(NEW, admin.password_hash) assert admin.token_version == version + 1 # Прежняя админская cookie больше не действует (в т.ч. у того, кто украл пароль). assert client.get("/api/admin/me").status_code == 401 assert _login(client, OLD).status_code == 401 assert _login(client, NEW).status_code == 200 def test_regular_bootstrap_keeps_admin_password_outside_dev(engine, make_admin, monkeypatch): """Обычный старт в prod по-прежнему не берёт пароль из .env — только ротация.""" make_admin("admin", OLD) monkeypatch.setattr(bootstrap.settings, "app_env", "production") monkeypatch.setattr(bootstrap.settings, "admin_password", NEW) with Session(engine) as s: bootstrap._ensure_admin(s) assert verify_password(OLD, _admin(engine).password_hash) def test_reset_refuses_without_admin_or_password(engine, make_admin, monkeypatch): monkeypatch.setattr(bootstrap.settings, "admin_password", NEW) with Session(engine) as s, pytest.raises(RuntimeError, match="Администратора ещё нет"): bootstrap.reset_admin_password(s) make_admin("admin", OLD) monkeypatch.setattr(bootstrap.settings, "admin_password", " ") with Session(engine) as s, pytest.raises(RuntimeError, match="ADMIN_PASSWORD пуст"): bootstrap.reset_admin_password(s) assert verify_password(OLD, _admin(engine).password_hash) def test_reset_refuses_when_bootstrap_disabled(engine, make_admin, monkeypatch): make_admin("admin", OLD) monkeypatch.setattr(bootstrap.settings, "admin_bootstrap_enabled", False) monkeypatch.setattr(bootstrap.settings, "admin_password", NEW) with Session(engine) as s, pytest.raises(RuntimeError, match="ADMIN_BOOTSTRAP_ENABLED"): bootstrap.reset_admin_password(s)