Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions api_schemas/user_schemas.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import TYPE_CHECKING, Annotated, Literal
from pydantic import StringConstraints
from pydantic import StringConstraints, field_validator
from helpers.check_stil_id import check_stil_id
from fastapi_users_pelicanq import schemas as fastapi_users_schemas
from api_schemas.post_schemas import PostRead
from helpers.constants import MAX_FIRST_NAME_LEN, MAX_LAST_NAME_LEN
Expand All @@ -11,6 +12,19 @@
from api_schemas.group_schema import GroupRead


class StilIdValidationMixin:
stil_id: str | None = None

@field_validator("stil_id", mode="after")
@classmethod
def validate_stil_id(cls, value: str | None) -> str | None:
if value is None:
return None
if not check_stil_id(value):
raise ValueError("Invalid stil-id")
return value


class _UserEventRead(BaseSchema):
id: int

Expand Down Expand Up @@ -118,25 +132,25 @@ class UserInGroupRead(BaseSchema):


# fastapi-users will take all fields on this model and feed into the user constructor User_DB(...) when /auth/register route is called
class UserCreate(fastapi_users_schemas.BaseUserCreate, BaseSchema):
class UserCreate(StilIdValidationMixin, fastapi_users_schemas.BaseUserCreate, BaseSchema):
first_name: Annotated[str, StringConstraints(max_length=MAX_FIRST_NAME_LEN)]
last_name: Annotated[str, StringConstraints(max_length=MAX_LAST_NAME_LEN)]
telephone_number: PhoneNumber
start_year: int | None = None
pass
# stil_id is here, but inherited from StilIdValidationMixin


class UserUpdate(BaseSchema):
class UserUpdate(StilIdValidationMixin, BaseSchema):
first_name: str | None = None
last_name: str | None = None
start_year: int | None = None
program: PROGRAM_TYPE | None = None
notifications: bool | None = None
stil_id: str | None = None
standard_food_preferences: list[str] | None = None
other_food_preferences: str | None = None
telephone_number: PhoneNumber | None = None
moose_game_name: str | None = None
# stil_id is here, but inherited from StilIdValidationMixin


class UpdateUserMember(BaseSchema):
Expand Down
8 changes: 8 additions & 0 deletions helpers/check_stil_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import re


def check_stil_id(s: str) -> bool:
if not len(s) == 10:
return False
pattern = r"^[a-z]{2}\d{4}[a-z]{2}-s$"
return bool(re.fullmatch(pattern, s))
9 changes: 1 addition & 8 deletions services/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,9 @@
from db_models.user_model import User_DB
from fastapi import HTTPException, status
from sqlalchemy.exc import DataError, NoResultFound, MultipleResultsFound
import re
from helpers.types import FOOD_PREFERENCES
from db_models.post_model import Post_DB


def check_stil_id(s: str) -> bool:
if not len(s) == 10:
return False
pattern = r"^[a-z]{2}\d{4}[a-z]{2}-s$"
return bool(re.fullmatch(pattern, s))
from helpers.check_stil_id import check_stil_id


def condition(model, asset):
Expand Down
13 changes: 13 additions & 0 deletions tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ def test_register_user(client, user1_data):
assert "id" in data


def test_register_user_invalid_stil_id(client):
"""Test user registration with valid data except for an invalid stil_id"""
user_data = user_data_factory(
email="test1@example.com",
last_name="User1",
program="F",
telephone_number="+46701234567",
stil_id="invalid_stil_id",
)
response = client.post("/auth/register", json=user_data)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY


def test_register_duplicate_user(client, user1_data):
"""Test registration with duplicate email fails"""
# Register first user
Expand Down
Loading