Skip to content

Commit

Permalink
handles errors
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov committed Dec 5, 2024
1 parent 9ce3607 commit d7921a8
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from .exceptions import (
AlreadyPreRegisteredError,
MissingGroupExtraPropertiesForProductError,
UserNameDuplicateError,
UserNotFoundError,
)
from .schemas import ProfileGet, ProfileUpdate
Expand All @@ -48,6 +49,10 @@ async def wrapper(request: web.Request) -> web.StreamResponse:

except UserNotFoundError as exc:
raise web.HTTPNotFound(reason=f"{exc}") from exc

except UserNameDuplicateError as exc:
raise web.HTTPConflict(reason=f"{exc}") from exc

except MissingGroupExtraPropertiesForProductError as exc:
error_code = exc.error_code()
user_error_msg = FMSG_MISSING_CONFIG_WITH_OEC.format(error_code=error_code)
Expand Down
22 changes: 18 additions & 4 deletions services/web/server/src/simcore_service_webserver/users/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from collections import deque
from typing import Any, NamedTuple, TypedDict

import simcore_postgres_database.errors as db_errors
import sqlalchemy as sa
from aiohttp import web
from aiopg.sa.engine import Engine
Expand All @@ -32,7 +33,11 @@
from ._api import get_user_credentials, get_user_invoice_address, set_user_as_deleted
from ._models import ToUserUpdateDB
from ._preferences_api import get_frontend_user_preferences_aggregation
from .exceptions import MissingGroupExtraPropertiesForProductError, UserNotFoundError
from .exceptions import (
MissingGroupExtraPropertiesForProductError,
UserNameDuplicateError,
UserNotFoundError,
)
from .schemas import ProfileGet, ProfileUpdate

_logger = logging.getLogger(__name__)
Expand All @@ -42,7 +47,7 @@ def _parse_as_user(user_id: Any) -> UserID:
try:
return TypeAdapter(UserID).validate_python(user_id)
except ValidationError as err:
raise UserNotFoundError(uid=user_id) from err
raise UserNotFoundError(uid=user_id, user_id=user_id) from err


async def get_user_profile(
Expand Down Expand Up @@ -158,14 +163,23 @@ async def update_user_profile(
"""
Raises:
UserNotFoundError
UserNameAlreadyExistsError
"""
user_id = _parse_as_user(user_id)

if updated_values := ToUserUpdateDB.from_api(update).to_columns():
async with get_database_engine(app).acquire() as conn:
query = users.update().where(users.c.id == user_id).values(**updated_values)
resp = await conn.execute(query)
assert resp.rowcount == 1 # nosec

try:

resp = await conn.execute(query)
assert resp.rowcount == 1 # nosec

except db_errors.UniqueViolation as err:
raise UserNameDuplicateError(
user_name=updated_values.get("name")
) from err


async def get_user_role(app: web.Application, user_id: UserID) -> UserRole:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def __init__(self, *, uid: int | None = None, email: str | None = None, **ctx: A
self.email = email


class UserNameDuplicateError(UsersBaseError):
msg_template = "Username {user_name} is already in use. Violates unique constraint"


class TokenNotFoundError(UsersBaseError):
msg_template = "Token for service {service_id} not found"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,17 @@ class ProfileUpdate(BaseModel):
@classmethod
def _validate_user_name(cls, value: str):
# Ensure valid characters (alphanumeric + . _ -)
if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", value):
msg = f"Username '{value}' must start with a letter and can only contain letters, numbers and '_'."
if not re.match(r"^[a-zA-Z][a-zA-Z0-9._-]*$", value):
msg = f"Username '{value}' must start with a letter and can only contain letters, numbers and '_', '.' or '-'."
raise ValueError(msg)

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

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

Expand Down
6 changes: 5 additions & 1 deletion services/web/server/tests/unit/with_dbs/03/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,16 @@ async def test_update_wrong_user_name(

@pytest.mark.parametrize("user_role", [UserRole.USER])
async def test_update_existing_user_name(
user: UserInfoDict,
logged_user: UserInfoDict,
client: TestClient,
user_role: UserRole,
):
assert client.app

other_username = user["name"]
assert other_username != logged_user["name"]

# update with SAME username (i.e. existing)
url = client.app.router["get_my_profile"].url_for()
resp = await client.get(f"{url}")
Expand All @@ -271,7 +275,7 @@ async def test_update_existing_user_name(
resp = await client.patch(
f"{url}",
json={
"userName": data["userName"],
"userName": other_username,
},
)
await assert_status(resp, status.HTTP_409_CONFLICT)
Expand Down

0 comments on commit d7921a8

Please sign in to comment.