Skip to content

Commit d7921a8

Browse files
committed
handles errors
1 parent 9ce3607 commit d7921a8

File tree

5 files changed

+36
-9
lines changed

5 files changed

+36
-9
lines changed

services/web/server/src/simcore_service_webserver/users/_handlers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from .exceptions import (
2626
AlreadyPreRegisteredError,
2727
MissingGroupExtraPropertiesForProductError,
28+
UserNameDuplicateError,
2829
UserNotFoundError,
2930
)
3031
from .schemas import ProfileGet, ProfileUpdate
@@ -48,6 +49,10 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
4849

4950
except UserNotFoundError as exc:
5051
raise web.HTTPNotFound(reason=f"{exc}") from exc
52+
53+
except UserNameDuplicateError as exc:
54+
raise web.HTTPConflict(reason=f"{exc}") from exc
55+
5156
except MissingGroupExtraPropertiesForProductError as exc:
5257
error_code = exc.error_code()
5358
user_error_msg = FMSG_MISSING_CONFIG_WITH_OEC.format(error_code=error_code)

services/web/server/src/simcore_service_webserver/users/api.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from collections import deque
1010
from typing import Any, NamedTuple, TypedDict
1111

12+
import simcore_postgres_database.errors as db_errors
1213
import sqlalchemy as sa
1314
from aiohttp import web
1415
from aiopg.sa.engine import Engine
@@ -32,7 +33,11 @@
3233
from ._api import get_user_credentials, get_user_invoice_address, set_user_as_deleted
3334
from ._models import ToUserUpdateDB
3435
from ._preferences_api import get_frontend_user_preferences_aggregation
35-
from .exceptions import MissingGroupExtraPropertiesForProductError, UserNotFoundError
36+
from .exceptions import (
37+
MissingGroupExtraPropertiesForProductError,
38+
UserNameDuplicateError,
39+
UserNotFoundError,
40+
)
3641
from .schemas import ProfileGet, ProfileUpdate
3742

3843
_logger = logging.getLogger(__name__)
@@ -42,7 +47,7 @@ def _parse_as_user(user_id: Any) -> UserID:
4247
try:
4348
return TypeAdapter(UserID).validate_python(user_id)
4449
except ValidationError as err:
45-
raise UserNotFoundError(uid=user_id) from err
50+
raise UserNotFoundError(uid=user_id, user_id=user_id) from err
4651

4752

4853
async def get_user_profile(
@@ -158,14 +163,23 @@ async def update_user_profile(
158163
"""
159164
Raises:
160165
UserNotFoundError
166+
UserNameAlreadyExistsError
161167
"""
162168
user_id = _parse_as_user(user_id)
163169

164170
if updated_values := ToUserUpdateDB.from_api(update).to_columns():
165171
async with get_database_engine(app).acquire() as conn:
166172
query = users.update().where(users.c.id == user_id).values(**updated_values)
167-
resp = await conn.execute(query)
168-
assert resp.rowcount == 1 # nosec
173+
174+
try:
175+
176+
resp = await conn.execute(query)
177+
assert resp.rowcount == 1 # nosec
178+
179+
except db_errors.UniqueViolation as err:
180+
raise UserNameDuplicateError(
181+
user_name=updated_values.get("name")
182+
) from err
169183

170184

171185
async def get_user_role(app: web.Application, user_id: UserID) -> UserRole:

services/web/server/src/simcore_service_webserver/users/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ def __init__(self, *, uid: int | None = None, email: str | None = None, **ctx: A
2121
self.email = email
2222

2323

24+
class UserNameDuplicateError(UsersBaseError):
25+
msg_template = "Username {user_name} is already in use. Violates unique constraint"
26+
27+
2428
class TokenNotFoundError(UsersBaseError):
2529
msg_template = "Token for service {service_id} not found"
2630

services/web/server/src/simcore_service_webserver/users/schemas.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,17 @@ class ProfileUpdate(BaseModel):
121121
@classmethod
122122
def _validate_user_name(cls, value: str):
123123
# Ensure valid characters (alphanumeric + . _ -)
124-
if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", value):
125-
msg = f"Username '{value}' must start with a letter and can only contain letters, numbers and '_'."
124+
if not re.match(r"^[a-zA-Z][a-zA-Z0-9._-]*$", value):
125+
msg = f"Username '{value}' must start with a letter and can only contain letters, numbers and '_', '.' or '-'."
126126
raise ValueError(msg)
127127

128128
# Ensure no consecutive special characters
129-
if re.search(r"[_]{2,}", value):
129+
if re.search(r"[_.-]{2,}", value):
130130
msg = f"Username '{value}' cannot contain consecutive special characters like '__'."
131131
raise ValueError(msg)
132132

133133
# Ensure it doesn't end with a special character
134-
if {value[0], value[-1]}.intersection({"_"}):
134+
if {value[0], value[-1]}.intersection({"_", "-", "."}):
135135
msg = f"Username '{value}' cannot end or start with a special character."
136136
raise ValueError(msg)
137137

services/web/server/tests/unit/with_dbs/03/test_users.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,16 @@ async def test_update_wrong_user_name(
254254

255255
@pytest.mark.parametrize("user_role", [UserRole.USER])
256256
async def test_update_existing_user_name(
257+
user: UserInfoDict,
257258
logged_user: UserInfoDict,
258259
client: TestClient,
259260
user_role: UserRole,
260261
):
261262
assert client.app
262263

264+
other_username = user["name"]
265+
assert other_username != logged_user["name"]
266+
263267
# update with SAME username (i.e. existing)
264268
url = client.app.router["get_my_profile"].url_for()
265269
resp = await client.get(f"{url}")
@@ -271,7 +275,7 @@ async def test_update_existing_user_name(
271275
resp = await client.patch(
272276
f"{url}",
273277
json={
274-
"userName": data["userName"],
278+
"userName": other_username,
275279
},
276280
)
277281
await assert_status(resp, status.HTTP_409_CONFLICT)

0 commit comments

Comments
 (0)