80 lines
3.0 KiB
Python
80 lines
3.0 KiB
Python
"""Bootstrap admin user on backend startup."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from sqlalchemy import delete, select
|
|
|
|
from cyclone.auth import bootstrap, users
|
|
from cyclone.auth.deps import AUTH_DISABLED
|
|
from cyclone.auth.permissions import Role
|
|
from cyclone.db import Session as DbSession
|
|
from cyclone.db import SessionLocal, User
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear(monkeypatch):
|
|
# Reset the bootstrap-side AUTH_DISABLED flag so tests don't leak state
|
|
# into each other. The conftest fixture flips this back to True at the
|
|
# start of every test, but bootstrap.run() mutates this module-level
|
|
# value, so we restore it here too.
|
|
monkeypatch.setattr("cyclone.auth.deps.AUTH_DISABLED", False)
|
|
# conftest sets CYCLONE_AUTH_DISABLED=1 at module import. Drop it
|
|
# by default so tests exercise the real bootstrap path; the
|
|
# AUTH_DISABLED-specific test re-sets it explicitly.
|
|
monkeypatch.delenv("CYCLONE_AUTH_DISABLED", raising=False)
|
|
with SessionLocal()() as db:
|
|
db.execute(delete(DbSession))
|
|
db.execute(delete(User))
|
|
db.commit()
|
|
yield
|
|
monkeypatch.setattr("cyclone.auth.deps.AUTH_DISABLED", False)
|
|
with SessionLocal()() as db:
|
|
db.execute(delete(DbSession))
|
|
db.execute(delete(User))
|
|
db.commit()
|
|
|
|
|
|
def test_bootstrap_creates_admin_when_users_empty_and_env_set(monkeypatch):
|
|
monkeypatch.setenv("CYCLONE_ADMIN_USERNAME", "firstadmin")
|
|
monkeypatch.setenv("CYCLONE_ADMIN_PASSWORD", "firstadminpw1")
|
|
bootstrap.run()
|
|
with SessionLocal()() as db:
|
|
u = db.execute(select(User).where(User.username == "firstadmin")).scalar_one()
|
|
assert u.role == Role.ADMIN.value
|
|
|
|
|
|
def test_bootstrap_noop_when_users_exist(monkeypatch):
|
|
monkeypatch.setenv("CYCLONE_ADMIN_USERNAME", "ignored")
|
|
monkeypatch.setenv("CYCLONE_ADMIN_PASSWORD", "ignoredignored1")
|
|
with SessionLocal()() as db:
|
|
users.create(db, username="existing", password="hunter2hunter2", role="admin")
|
|
bootstrap.run()
|
|
with SessionLocal()() as db:
|
|
all_users = db.execute(select(User)).scalars().all()
|
|
usernames = {u.username for u in all_users}
|
|
assert usernames == {"existing"}
|
|
|
|
|
|
def test_bootstrap_refuses_to_run_without_env(monkeypatch):
|
|
monkeypatch.delenv("CYCLONE_ADMIN_USERNAME", raising=False)
|
|
monkeypatch.delenv("CYCLONE_ADMIN_PASSWORD", raising=False)
|
|
with pytest.raises(RuntimeError, match="CYCLONE_ADMIN_USERNAME"):
|
|
bootstrap.run()
|
|
|
|
|
|
def test_bootstrap_rejects_short_password(monkeypatch):
|
|
monkeypatch.setenv("CYCLONE_ADMIN_USERNAME", "weak")
|
|
monkeypatch.setenv("CYCLONE_ADMIN_PASSWORD", "short")
|
|
with pytest.raises(RuntimeError, match="12 characters"):
|
|
bootstrap.run()
|
|
|
|
|
|
def test_bootstrap_skips_when_auth_disabled(monkeypatch):
|
|
monkeypatch.setenv("CYCLONE_AUTH_DISABLED", "1")
|
|
# Even without env vars, bootstrap should NOT raise when AUTH_DISABLED=1.
|
|
bootstrap.run()
|
|
# And it must flip the deps flag so the API skips auth checks.
|
|
from cyclone.auth import deps as _deps
|
|
assert _deps.AUTH_DISABLED is True
|