e8fe4845aa
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
3.7 KiB
Python
90 lines
3.7 KiB
Python
# tests/test_operator_authenticate.py
|
|
import time
|
|
from datetime import datetime
|
|
import pytest
|
|
|
|
from backend.operator_auth import (
|
|
authenticate, create_operator, reset_operator_password,
|
|
set_operator_active, set_operator_role, change_own_password, MAX_LOGIN_FAILURES,
|
|
)
|
|
from backend.auth_passwords import verify_password
|
|
from backend.models import OperatorUser
|
|
|
|
|
|
def test_create_operator_generates_temp_and_forces_change(db_session):
|
|
user, raw = create_operator(db_session, "Dad@X.com", "Dad", "admin")
|
|
assert user.email == "dad@x.com" # lowercased
|
|
assert user.must_change_password is True
|
|
assert verify_password(raw, user.password_hash)
|
|
|
|
|
|
def test_create_operator_with_explicit_password_no_forced_change(db_session):
|
|
user, raw = create_operator(db_session, "brian@x.com", "Brian", "superadmin", password="chosen-pw-123")
|
|
assert raw == "chosen-pw-123"
|
|
assert user.must_change_password is False
|
|
|
|
|
|
def test_create_operator_rejects_duplicate_and_bad_role(db_session):
|
|
create_operator(db_session, "a@x.com", "A", "admin")
|
|
with pytest.raises(ValueError):
|
|
create_operator(db_session, "A@x.com", "A2", "admin") # dup (case-insensitive)
|
|
with pytest.raises(ValueError):
|
|
create_operator(db_session, "b@x.com", "B", "wizard") # bad role
|
|
|
|
|
|
def test_authenticate_success(db_session):
|
|
user, raw = create_operator(db_session, "ok@x.com", "Ok", "admin", password="rightpw-9")
|
|
got, status = authenticate(db_session, "OK@x.com", "rightpw-9")
|
|
assert status == "ok" and got.id == user.id
|
|
assert got.last_login_at is not None
|
|
assert got.failed_login_count == 0
|
|
|
|
|
|
def test_authenticate_wrong_password_counts(db_session):
|
|
create_operator(db_session, "wp@x.com", "Wp", "admin", password="rightpw-9")
|
|
got, status = authenticate(db_session, "wp@x.com", "nope")
|
|
assert got is None and status == "bad"
|
|
assert db_session.query(OperatorUser).filter_by(email="wp@x.com").first().failed_login_count == 1
|
|
|
|
|
|
def test_lockout_after_five_then_correct_password_refused(db_session):
|
|
create_operator(db_session, "lk@x.com", "Lk", "admin", password="rightpw-9")
|
|
for _ in range(MAX_LOGIN_FAILURES):
|
|
authenticate(db_session, "lk@x.com", "nope")
|
|
got, status = authenticate(db_session, "lk@x.com", "rightpw-9") # correct, but locked
|
|
assert got is None and status == "locked"
|
|
|
|
|
|
def test_authenticate_unknown_email_is_bad_not_error(db_session):
|
|
got, status = authenticate(db_session, "ghost@x.com", "whatever")
|
|
assert got is None and status == "bad"
|
|
|
|
|
|
def test_reset_password_sets_new_hash_forces_change_and_bumps_sessions(db_session):
|
|
user, _ = create_operator(db_session, "r@x.com", "R", "admin", password="orig-pw-1")
|
|
before = user.sessions_valid_from
|
|
raw = reset_operator_password(db_session, user)
|
|
assert verify_password(raw, user.password_hash)
|
|
assert user.must_change_password is True
|
|
assert user.sessions_valid_from >= before
|
|
|
|
|
|
def test_change_own_password_clears_flag_and_bumps(db_session):
|
|
user, _ = create_operator(db_session, "c@x.com", "C", "admin", password="orig-pw-1")
|
|
user.must_change_password = True
|
|
db_session.commit()
|
|
new_iat = change_own_password(db_session, user, "brand-new-pw-2")
|
|
assert verify_password("brand-new-pw-2", user.password_hash)
|
|
assert user.must_change_password is False
|
|
assert user.sessions_valid_from == datetime.utcfromtimestamp(new_iat)
|
|
|
|
|
|
def test_set_active_and_role(db_session):
|
|
user, _ = create_operator(db_session, "s@x.com", "S", "admin", password="orig-pw-1")
|
|
set_operator_active(db_session, user, False)
|
|
assert user.active is False
|
|
set_operator_role(db_session, user, "superadmin")
|
|
assert user.role == "superadmin"
|
|
with pytest.raises(ValueError):
|
|
set_operator_role(db_session, user, "wizard")
|