Skip to content

Commit 9403609

Browse files
authored
🐛 Fixes error upon registration of pending email confirmation (#5488)
1 parent 50ac071 commit 9403609

File tree

4 files changed

+66
-24
lines changed

4 files changed

+66
-24
lines changed

packages/postgres-database/src/simcore_postgres_database/errors.py

+2-11
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,13 @@
3232
from psycopg2.errors import (
3333
CheckViolation,
3434
ForeignKeyViolation,
35+
InvalidTextRepresentation,
3536
NotNullViolation,
3637
UniqueViolation,
3738
)
3839

3940
assert issubclass(UniqueViolation, IntegrityError) # nosec
4041

41-
# TODO: see https://stackoverflow.com/questions/58740043/how-do-i-catch-a-psycopg2-errors-uniqueviolation-error-in-a-python-flask-app
42-
# from sqlalchemy.exc import IntegrityError
43-
#
44-
# from psycopg2.errors import UniqueViolation
45-
#
46-
# try:
47-
# s.commit()
48-
# except IntegrityError as e:
49-
# assert isinstance(e.orig, UniqueViolation)
50-
51-
5242
__all__: tuple[str, ...] = (
5343
"CheckViolation",
5444
"DatabaseError",
@@ -58,6 +48,7 @@
5848
"IntegrityError",
5949
"InterfaceError",
6050
"InternalError",
51+
"InvalidTextRepresentation",
6152
"NotNullViolation",
6253
"NotSupportedError",
6354
"OperationalError",

packages/postgres-database/src/simcore_postgres_database/models/users.py

+7-10
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,16 @@ def __lt__(self, other: "UserRole") -> bool:
5050
return NotImplemented
5151

5252

53-
class UserStatus(Enum):
54-
"""
55-
pending: user registered but not confirmed
56-
active: user is confirmed and can use the platform
57-
expired: user is not authorized because it expired after a trial period
58-
banned: user is not authorized
59-
deleted: this account is marked for deletion
60-
"""
61-
62-
CONFIRMATION_PENDING = "PENDING"
53+
class UserStatus(str, Enum):
54+
# This is a transition state. The user is registered but not confirmed. NOTE that state is optional depending on LOGIN_REGISTRATION_CONFIRMATION_REQUIRED
55+
CONFIRMATION_PENDING = "CONFIRMATION_PENDING"
56+
# This user can now operate the platform
6357
ACTIVE = "ACTIVE"
58+
# This user is inactive because it expired after a trial period
6459
EXPIRED = "EXPIRED"
60+
# This user is inactive because he has been a bad boy
6561
BANNED = "BANNED"
62+
# This user is inactive because it was marked for deletion
6663
DELETED = "DELETED"
6764

6865

packages/postgres-database/tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
)
3333

3434
pytest_plugins = [
35-
"pytest_simcore.repository_paths",
3635
"pytest_simcore.pytest_global_environs",
36+
"pytest_simcore.repository_paths",
3737
]
3838

3939

packages/postgres-database/tests/test_users.py

+56-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from aiopg.sa.result import ResultProxy, RowProxy
1212
from faker import Faker
1313
from pytest_simcore.helpers.rawdata_fakers import random_user
14-
from simcore_postgres_database.errors import UniqueViolation
14+
from simcore_postgres_database.errors import InvalidTextRepresentation, UniqueViolation
1515
from simcore_postgres_database.models.users import (
1616
_USER_ROLE_TO_LEVEL,
1717
UserRole,
@@ -101,10 +101,64 @@ def test_user_roles_compares():
101101
@pytest.fixture
102102
async def clean_users_db_table(connection: SAConnection):
103103
yield
104-
105104
await connection.execute(users.delete())
106105

107106

107+
async def test_user_status_as_pending(
108+
connection: SAConnection, faker: Faker, clean_users_db_table: None
109+
):
110+
"""Checks a bug where the expression
111+
112+
`user_status = UserStatus(user["status"])`
113+
114+
raise ValueError because **before** this change `UserStatus.CONFIRMATION_PENDING.value == "PENDING"`
115+
"""
116+
# after changing to UserStatus.CONFIRMATION_PENDING == "CONFIRMATION_PENDING"
117+
with pytest.raises(ValueError): # noqa: PT011
118+
assert UserStatus("PENDING") == UserStatus.CONFIRMATION_PENDING
119+
120+
assert UserStatus("CONFIRMATION_PENDING") == UserStatus.CONFIRMATION_PENDING
121+
assert UserStatus.CONFIRMATION_PENDING.value == "CONFIRMATION_PENDING"
122+
assert UserStatus.CONFIRMATION_PENDING == "CONFIRMATION_PENDING"
123+
assert str(UserStatus.CONFIRMATION_PENDING) == "UserStatus.CONFIRMATION_PENDING"
124+
125+
# tests that the database never stores the word "PENDING"
126+
data = random_user(faker, status="PENDING")
127+
assert data["status"] == "PENDING"
128+
with pytest.raises(InvalidTextRepresentation) as err_info:
129+
await connection.execute(users.insert().values(data))
130+
131+
assert 'invalid input value for enum userstatus: "PENDING"' in f"{err_info.value}"
132+
133+
134+
@pytest.mark.parametrize(
135+
"status_value",
136+
[
137+
UserStatus.CONFIRMATION_PENDING,
138+
"CONFIRMATION_PENDING",
139+
],
140+
)
141+
async def test_user_status_inserted_as_enum_or_int(
142+
status_value: UserStatus | str,
143+
connection: SAConnection,
144+
faker: Faker,
145+
clean_users_db_table: None,
146+
):
147+
# insert as `status_value`
148+
data = random_user(faker, status=status_value)
149+
assert data["status"] == status_value
150+
user_id = await connection.scalar(users.insert().values(data).returning(users.c.id))
151+
152+
# get as UserStatus.CONFIRMATION_PENDING
153+
user = await (
154+
await connection.execute(users.select().where(users.c.id == user_id))
155+
).first()
156+
assert user
157+
158+
assert UserStatus(user.status) == UserStatus.CONFIRMATION_PENDING
159+
assert user.status == UserStatus.CONFIRMATION_PENDING
160+
161+
108162
async def test_unique_username(
109163
connection: SAConnection, faker: Faker, clean_users_db_table: None
110164
):

0 commit comments

Comments
 (0)