8fc3d9adda
- New test_existing_endpoints_require_auth.py: spot-check that existing /api/* endpoints now require auth (gated via Depends(matrix_gate)) when AUTH_DISABLED is False. Health remains public. - conftest.py: flip AUTH_DISABLED=True for the suite so the legacy pre-auth tests keep passing without login. Auth tests flip it back off via their own autouse fixture (now patched to use monkeypatch for cleanup). Verified: 53 auth tests pass; 222 pre-existing non-auth failures are unchanged.
110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
"""Unit tests for cyclone.auth.users — User CRUD + bcrypt hashing."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from sqlalchemy import delete
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
from cyclone.auth import users
|
|
from cyclone.db import SessionLocal, User
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_users(monkeypatch):
|
|
# conftest sets AUTH_DISABLED=True so pre-auth tests keep passing
|
|
# without a login. The auth-users tests need the real auth path
|
|
# exercised, so flip it back off here.
|
|
monkeypatch.setattr("cyclone.auth.deps.AUTH_DISABLED", False)
|
|
with SessionLocal()() as db:
|
|
db.execute(delete(User))
|
|
db.commit()
|
|
yield
|
|
with SessionLocal()() as db:
|
|
db.execute(delete(User))
|
|
db.commit()
|
|
|
|
|
|
def test_hash_password_returns_bcrypt():
|
|
h = users.hash_password("hunter2hunter2")
|
|
assert h.startswith("$2")
|
|
|
|
|
|
def test_hash_password_produces_unique_salts():
|
|
a = users.hash_password("same-password")
|
|
b = users.hash_password("same-password")
|
|
assert a != b
|
|
|
|
|
|
def test_verify_password_correct():
|
|
h = users.hash_password("hunter2hunter2")
|
|
assert users.verify_password("hunter2hunter2", h) is True
|
|
|
|
|
|
def test_verify_password_incorrect():
|
|
h = users.hash_password("hunter2hunter2")
|
|
assert users.verify_password("WRONG", h) is False
|
|
|
|
|
|
def test_create_user_persists_with_hashed_password():
|
|
with SessionLocal()() as db:
|
|
u = users.create(db, username="alice", password="hunter2hunter2", role="admin")
|
|
assert u.id is not None
|
|
assert u.username == "alice"
|
|
assert u.role == "admin"
|
|
assert u.disabled_at is None
|
|
assert u.password_hash != "hunter2hunter2"
|
|
assert u.password_hash.startswith("$2")
|
|
|
|
|
|
def test_create_user_rejects_duplicate_username():
|
|
with SessionLocal()() as db:
|
|
users.create(db, username="bob", password="hunter2hunter2", role="user")
|
|
with SessionLocal()() as db:
|
|
with pytest.raises(IntegrityError):
|
|
users.create(db, username="bob", password="anotherone", role="user")
|
|
|
|
|
|
def test_get_by_username_returns_user():
|
|
with SessionLocal()() as db:
|
|
users.create(db, username="carol", password="hunter2hunter2", role="viewer")
|
|
with SessionLocal()() as db:
|
|
u = users.get_by_username(db, "carol")
|
|
assert u is not None
|
|
assert u.username == "carol"
|
|
|
|
|
|
def test_get_by_username_returns_none_for_missing():
|
|
with SessionLocal()() as db:
|
|
assert users.get_by_username(db, "ghost") is None
|
|
|
|
|
|
def test_disable_user_sets_disabled_at():
|
|
with SessionLocal()() as db:
|
|
u = users.create(db, username="dave", password="hunter2hunter2", role="user")
|
|
users.disable(db, u.id)
|
|
with SessionLocal()() as db:
|
|
refreshed = users.get_by_username(db, "dave")
|
|
assert refreshed.disabled_at is not None
|
|
|
|
|
|
def test_to_public_shape_omits_password_hash():
|
|
with SessionLocal()() as db:
|
|
u = users.create(db, username="eve", password="hunter2hunter2", role="admin")
|
|
shape = users.to_public(u)
|
|
assert "password_hash" not in shape
|
|
assert shape["username"] == "eve"
|
|
assert shape["role"] == "admin"
|
|
assert "id" in shape and "createdAt" in shape
|
|
|
|
|
|
def test_update_password_actually_rehashes_and_verifies():
|
|
with SessionLocal()() as db:
|
|
u = users.create(db, username="frank", password="hunter2hunter2", role="user")
|
|
with SessionLocal()() as db:
|
|
users.update_password(db, u.id, "newpassword1")
|
|
# Old password no longer verifies.
|
|
with SessionLocal()() as db:
|
|
refreshed = users.get_by_username(db, "frank")
|
|
assert not users.verify_password("hunter2hunter2", refreshed.password_hash)
|
|
assert users.verify_password("newpassword1", refreshed.password_hash) |