← full-stack-fastapi-template  /  backend/tests/crud/test_user.py

1
from fastapi.encoders import jsonable_encoder
2
from pwdlib.hashers.bcrypt import BcryptHasher
3
from sqlmodel import Session
4
5
from app import crud
6
from app.core.security import verify_password
7
from app.models import User, UserCreate, UserUpdate
8
from tests.utils.utils import random_email, random_lower_string
9
10
11
def test_create_user(db: Session) -> None:
12
    email = random_email()
13
    password = random_lower_string()
14
    user_in = UserCreate(email=email, password=password)
15
    user = crud.create_user(session=db, user_create=user_in)
16
    assert user.email == email
17
    assert hasattr(user, "hashed_password")
18
19
20
def test_authenticate_user(db: Session) -> None:
21
    email = random_email()
22
    password = random_lower_string()
23
    user_in = UserCreate(email=email, password=password)
24
    user = crud.create_user(session=db, user_create=user_in)
25
    authenticated_user = crud.authenticate(session=db, email=email, password=password)
26
    assert authenticated_user
27
    assert user.email == authenticated_user.email
28
29
30
def test_not_authenticate_user(db: Session) -> None:
31
    email = random_email()
32
    password = random_lower_string()
33
    user = crud.authenticate(session=db, email=email, password=password)
34
    assert user is None
35
36
37
def test_check_if_user_is_active(db: Session) -> None:
38
    email = random_email()
39
    password = random_lower_string()
40
    user_in = UserCreate(email=email, password=password)
41
    user = crud.create_user(session=db, user_create=user_in)
42
    assert user.is_active is True
43
44
45
def test_check_if_user_is_active_inactive(db: Session) -> None:
46
    email = random_email()
47
    password = random_lower_string()
48
    user_in = UserCreate(email=email, password=password, is_active=False)
49
    user = crud.create_user(session=db, user_create=user_in)
50
    assert user.is_active is False
51
52
53
def test_check_if_user_is_superuser(db: Session) -> None:
54
    email = random_email()
55
    password = random_lower_string()
56
    user_in = UserCreate(email=email, password=password, is_superuser=True)
57
    user = crud.create_user(session=db, user_create=user_in)
58
    assert user.is_superuser is True
59
60
61
def test_check_if_user_is_superuser_normal_user(db: Session) -> None:
62
    username = random_email()
63
    password = random_lower_string()
64
    user_in = UserCreate(email=username, password=password)
65
    user = crud.create_user(session=db, user_create=user_in)
66
    assert user.is_superuser is False
67
68
69
def test_get_user(db: Session) -> None:
70
    password = random_lower_string()
71
    username = random_email()
72
    user_in = UserCreate(email=username, password=password, is_superuser=True)
73
    user = crud.create_user(session=db, user_create=user_in)
74
    user_2 = db.get(User, user.id)
75
    assert user_2
76
    assert user.email == user_2.email
77
    assert jsonable_encoder(user) == jsonable_encoder(user_2)
78
79
80
def test_update_user(db: Session) -> None:
81
    password = random_lower_string()
82
    email = random_email()
83
    user_in = UserCreate(email=email, password=password, is_superuser=True)
84
    user = crud.create_user(session=db, user_create=user_in)
85
    new_password = random_lower_string()
86
    user_in_update = UserUpdate(password=new_password, is_superuser=True)
87
    if user.id is not None:
88
        crud.update_user(session=db, db_user=user, user_in=user_in_update)
89
    user_2 = db.get(User, user.id)
90
    assert user_2
91
    assert user.email == user_2.email
92
    verified, _ = verify_password(new_password, user_2.hashed_password)
93
    assert verified
94
95
96
def test_authenticate_user_with_bcrypt_upgrades_to_argon2(db: Session) -> None:
97
    """Test that a user with bcrypt password hash gets upgraded to argon2 on login."""
98
    email = random_email()
99
    password = random_lower_string()
100
101
    # Create a bcrypt hash directly (simulating legacy password)
102
    bcrypt_hasher = BcryptHasher()
103
    bcrypt_hash = bcrypt_hasher.hash(password)
104
    assert bcrypt_hash.startswith("$2")  # bcrypt hashes start with $2
105
106
    # Create user with bcrypt hash directly in the database
107
    user = User(email=email, hashed_password=bcrypt_hash)
108
    db.add(user)
109
    db.commit()
110
    db.refresh(user)
111
112
    # Verify the hash is bcrypt before authentication
113
    assert user.hashed_password.startswith("$2")
114
115
    # Authenticate - this should upgrade the hash to argon2
116
    authenticated_user = crud.authenticate(session=db, email=email, password=password)
117
    assert authenticated_user
118
    assert authenticated_user.email == email
119
120
    db.refresh(authenticated_user)
121
122
    # Verify the hash was upgraded to argon2
123
    assert authenticated_user.hashed_password.startswith("$argon2")
124
125
    verified, updated_hash = verify_password(
126
        password, authenticated_user.hashed_password
127
    )
128
    assert verified
129
    # Should not need another update since it's already argon2
130
    assert updated_hash is None
131