feat(auth): users module with bcrypt hashing + CRUD
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""Unit tests for cyclone.auth.users — User CRUD + bcrypt hashing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import delete
|
||||
|
||||
from cyclone.auth import users
|
||||
from cyclone.db import SessionLocal, User
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear_users():
|
||||
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(Exception):
|
||||
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
|
||||
Reference in New Issue
Block a user