diff --git a/requirements.dev.txt b/requirements.dev.txt index 7a0bc5b4..4283c55a 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -2,4 +2,4 @@ flake8==6.0.0 pytest==7.2.1 httpx==0.23.3 coverage==7.2.3 -polyfactory==2.0.0 +polyfactory==2.7.2 diff --git a/requirements.txt b/requirements.txt index b778a103..cfb1f76f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,12 @@ redis==4.4.4 -fastapi==0.97.0 -uvicorn[standard]==0.20.0 +fastapi==0.101.0 +uvicorn[standard]==0.23.2 pyjwt[crypto]==2.6.0 -fastapi_mail==1.2.5 cfenv==0.5.3 SQLAlchemy==2.0.5.post1 psycopg2==2.9.5 alembic==1.10.2 PyMuPDF==1.21.1 +pydantic-settings==2.0.2 +email-validator==2.0.0.post2 python-multipart==0.0.6 diff --git a/training-front-end/package-lock.json b/training-front-end/package-lock.json index 9bc71a46..be420fb5 100644 --- a/training-front-end/package-lock.json +++ b/training-front-end/package-lock.json @@ -3229,7 +3229,8 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "node_modules/accepts": { "version": "1.3.8", @@ -11305,7 +11306,8 @@ "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, "node_modules/inline-style-parser": { "version": "0.1.1", @@ -14891,6 +14893,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, "dependencies": { "abbrev": "^1.0.0" }, diff --git a/training/api/api_v1/auth.py b/training/api/api_v1/auth.py index 7d96965d..c8517dce 100644 --- a/training/api/api_v1/auth.py +++ b/training/api/api_v1/auth.py @@ -34,7 +34,7 @@ def auth_exchange( detail="Invalid user." ) - user = User.from_orm(db_user) + user = User.model_validate(db_user) if not user.is_admin(): logging.info(f"UAA authenticated, but not an admin: {uaa_user['email']}") raise HTTPException( @@ -42,7 +42,7 @@ def auth_exchange( detail="Not authorized to login." ) - jwt_user = UserJWT.from_orm(db_user) - encoded_jwt = jwt.encode(jwt_user.dict(), settings.JWT_SECRET, algorithm="HS256") + jwt_user = UserJWT.model_validate(db_user) + encoded_jwt = jwt.encode(jwt_user.model_dump(), settings.JWT_SECRET, algorithm="HS256") logging.info(f"Token exchange success for {db_user.email}") return {'user': jwt_user, 'jwt': encoded_jwt} diff --git a/training/api/api_v1/loginless_flow.py b/training/api/api_v1/loginless_flow.py index f4ba460d..56a8dc48 100644 --- a/training/api/api_v1/loginless_flow.py +++ b/training/api/api_v1/loginless_flow.py @@ -2,7 +2,7 @@ import jwt from typing import Union -from fastapi import APIRouter, status, Response, HTTPException, Depends +from fastapi import APIRouter, status, Response, HTTPException, Depends, Request from training.schemas import TempUser, IncompleteTempUser, WebDestination, UserJWT from training.data import UserCache from training.repositories import UserRepository @@ -32,14 +32,18 @@ def page_lookup(): @router.post("/get-link", status_code=status.HTTP_201_CREATED) -def send_link( +async def send_link( response: Response, + request: Request, user: Union[TempUser, IncompleteTempUser], dest: WebDestination, repo: UserRepository = Depends(user_repository), cache: UserCache = Depends(UserCache), page_id_lookup: dict = Depends(page_lookup) ): + tu = await request.json() + print("res: ", tu) + print("user: ", type(user)) try: required_roles = page_id_lookup[dest.page_id]['required_roles'] except KeyError: @@ -63,7 +67,7 @@ def send_link( detail="Unauthorized" ) - user = TempUser.parse_obj({ + user = TempUser.model_validate({ "name": user_from_db.name, "email": user_from_db.email, "agency_id": user_from_db.agency_id, @@ -118,7 +122,7 @@ async def get_user( db_user = repo.find_by_email(user.email) if not db_user: db_user = repo.create(user) - user_return = UserJWT.from_orm(db_user) + user_return = UserJWT.model_validate(db_user) logging.info(f"Confirmed email token for {user.email}") - encoded_jwt = jwt.encode(user_return.dict(), settings.JWT_SECRET, algorithm="HS256") + encoded_jwt = jwt.encode(user_return.model_dump(), settings.JWT_SECRET, algorithm="HS256") return {'user': user_return, 'jwt': encoded_jwt} diff --git a/training/config.py b/training/config.py index 30ff004e..14efb5b4 100644 --- a/training/config.py +++ b/training/config.py @@ -1,6 +1,8 @@ -from pydantic import BaseSettings, EmailStr +from typing import Tuple, Type +from pydantic import EmailStr from typing import Dict, Any from cfenv import AppEnv +from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict def vcap_services_settings(settings: BaseSettings) -> Dict[str, Any]: @@ -55,7 +57,10 @@ class Settings(BaseSettings): SMTP_USER: str | None SMTP_SERVER: str SMTP_PORT: int - EMAIL_FROM: EmailStr = EmailStr("smartpay-noreply@gsa.gov") + SMTP_STARTTLS: bool + SMTP_SSL_TLS: bool + + EMAIL_FROM: EmailStr = "smartpay-noreply@gsa.gov" EMAIL_FROM_NAME: str = "GSA SmartPay" EMAIL_SUBJECT: str = "GSA SmartPay Training" @@ -74,18 +79,26 @@ class Settings(BaseSettings): AUTH_CLIENT_ID: str AUTH_AUTHORITY_URL: str - class Config: - env_file = '.env' - env_file_encoding = 'utf-8' - - @classmethod - def customise_sources(cls, init_settings, env_settings, file_secret_settings): - return ( - init_settings, - env_settings, - file_secret_settings, - vcap_services_settings, - ) + model_config = SettingsConfigDict( + env_file='.env', + env_file_encoding='utf-8' + ) + + @classmethod + def customise_sources( + cls, + settings_cls: Type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource + ) -> Tuple[PydanticBaseSettingsSource, ...]: + return ( + init_settings, + env_settings, + file_secret_settings, + vcap_services_settings, + ) settings = Settings() # type: ignore diff --git a/training/data/user_cache.py b/training/data/user_cache.py index 066f7f48..0fdd5c6f 100644 --- a/training/data/user_cache.py +++ b/training/data/user_cache.py @@ -32,7 +32,7 @@ def get(self, token: str) -> Optional[UserCreate]: def set(self, user: TempUser) -> str: token = str(uuid4()) - user_str = json.dumps(user.dict()) + user_str = json.dumps(user.model_dump()) # try/except here redis.set(token, user_str) redis.expire(token, self.CACHE_TTL) diff --git a/training/repositories/quiz.py b/training/repositories/quiz.py index e812fcb6..9a5952d9 100644 --- a/training/repositories/quiz.py +++ b/training/repositories/quiz.py @@ -9,7 +9,7 @@ def __init__(self, session: Session): super().__init__(session, models.Quiz) def create(self, quiz: schemas.QuizCreate) -> models.Quiz: - content_dict = quiz.content.dict() + content_dict = quiz.content.model_dump() # Assign IDs to questions and choices for qindex, question in enumerate(content_dict.get("questions", [])): diff --git a/training/schemas/agency.py b/training/schemas/agency.py index 19f85e83..0b5d18d1 100644 --- a/training/schemas/agency.py +++ b/training/schemas/agency.py @@ -1,10 +1,10 @@ -from pydantic import BaseModel -from pydantic.schema import Optional +from typing import Optional +from pydantic import ConfigDict, BaseModel class AgencyBase(BaseModel): name: str - bureau: Optional[str] + bureau: Optional[str] = None class AgencyCreate(AgencyBase): @@ -13,9 +13,7 @@ class AgencyCreate(AgencyBase): class Agency(AgencyBase): id: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class Bureau(BaseModel): diff --git a/training/schemas/quiz.py b/training/schemas/quiz.py index 83518464..1e88131c 100644 --- a/training/schemas/quiz.py +++ b/training/schemas/quiz.py @@ -1,5 +1,5 @@ from enum import Enum -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel from training.schemas import QuizContent, QuizContentCreate, QuizContentPublic @@ -29,13 +29,9 @@ class QuizCreate(QuizBase): class QuizPublic(QuizBase): id: int content: QuizContentPublic - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class Quiz(QuizBase): id: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/schemas/quiz_choice.py b/training/schemas/quiz_choice.py index c26d445d..58d733cb 100644 --- a/training/schemas/quiz_choice.py +++ b/training/schemas/quiz_choice.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel class QuizChoiceBase(BaseModel): @@ -16,6 +16,4 @@ class QuizChoicePublic(QuizChoiceBase): class QuizChoice(QuizChoiceBase): id: int correct: bool - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/schemas/quiz_completion.py b/training/schemas/quiz_completion.py index 3943ce53..3c90f0d4 100644 --- a/training/schemas/quiz_completion.py +++ b/training/schemas/quiz_completion.py @@ -1,5 +1,5 @@ from datetime import datetime -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel class QuizCompletionBase(BaseModel): @@ -15,6 +15,4 @@ class QuizCompletionCreate(QuizCompletionBase): class QuizCompletion(QuizCompletionBase): id: int submit_ts: datetime - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/schemas/quiz_grade.py b/training/schemas/quiz_grade.py index ae98ec36..725be150 100644 --- a/training/schemas/quiz_grade.py +++ b/training/schemas/quiz_grade.py @@ -9,7 +9,7 @@ class QuizGradeQuestion(BaseModel): class QuizGrade(BaseModel): - quiz_completion_id: int | None + quiz_completion_id: int | None = None quiz_id: int correct_count: int question_count: int diff --git a/training/schemas/quiz_question.py b/training/schemas/quiz_question.py index e8a52380..ed0beec5 100644 --- a/training/schemas/quiz_question.py +++ b/training/schemas/quiz_question.py @@ -1,5 +1,5 @@ from enum import Enum -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel from training.schemas import QuizChoice, QuizChoicePublic, QuizChoiceCreate @@ -25,6 +25,4 @@ class QuizQuestionPublic(QuizQuestionBase): class QuizQuestion(QuizQuestionBase): id: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/schemas/report_user_x_agency.py b/training/schemas/report_user_x_agency.py index 491206d7..517f0e64 100644 --- a/training/schemas/report_user_x_agency.py +++ b/training/schemas/report_user_x_agency.py @@ -1,9 +1,7 @@ -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel class ReportUserXAgency(BaseModel): user_id: int agency_id: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/schemas/role.py b/training/schemas/role.py index 08207e91..388430c0 100644 --- a/training/schemas/role.py +++ b/training/schemas/role.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel class RoleCreate(BaseModel): @@ -7,6 +7,4 @@ class RoleCreate(BaseModel): class Role(RoleCreate): id: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/schemas/temp_user.py b/training/schemas/temp_user.py index 7d5e8d04..b46b5dc8 100644 --- a/training/schemas/temp_user.py +++ b/training/schemas/temp_user.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, EmailStr +from pydantic import ConfigDict, BaseModel, EmailStr, field_validator class TempUser(BaseModel): @@ -9,9 +9,21 @@ class TempUser(BaseModel): email: EmailStr name: str agency_id: int + model_config = ConfigDict(from_attributes=True) - class Config: - orm_mode = True + @field_validator("agency_id", mode="before") + @classmethod + def to_int(cls, value: str | int) -> int: + ''' + This addresses a bug in pydantic that does not correctly choose + the right value from the union[TempUser, IncompleteTempUser] + when agency_id is a string. Related: + https://docs.pydantic.dev/dev-v2/migration/#unions + ''' + if isinstance(value, str): + value = int(value) + + return value class IncompleteTempUser(BaseModel): diff --git a/training/schemas/user.py b/training/schemas/user.py index e46330e2..d4bd44f7 100644 --- a/training/schemas/user.py +++ b/training/schemas/user.py @@ -1,14 +1,9 @@ from datetime import datetime -from pydantic import BaseModel, EmailStr, validator +from pydantic import ConfigDict, BaseModel, EmailStr, field_validator from training.schemas.agency import Agency from training.schemas.role import Role -def convert_roles(cls, input) -> list[str]: - # Converts roles from a list of dicts to a simple list of role name strings. - return [role.name for role in input] - - class UserBase(BaseModel): email: EmailStr name: str @@ -29,31 +24,29 @@ class User(UserBase): def is_admin(self) -> bool: role_names = [role.name.upper() for role in self.roles] return "Admin".upper() in role_names - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class UserJWT(User): # Provides a user object that is appropriate for encoding into a JWT. roles: list[str] - _roles_validator = validator("roles", pre=True, allow_reuse=True)(convert_roles) + + @field_validator('roles', mode='before') + def convert_roles(cls, input) -> list[str]: + # Converts roles from a list of dicts to a simple list of role name strings. + return [role.name for role in input] class UserQuizCompletionReportData(UserBase): agency: str - bureau: str | None + bureau: str | None = None quiz: str completion_date: datetime - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class UserSearchResult(BaseModel): users: list[User] total_count: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/schemas/user_cerificate.py b/training/schemas/user_cerificate.py index e3c6aeef..4d92ceaf 100644 --- a/training/schemas/user_cerificate.py +++ b/training/schemas/user_cerificate.py @@ -1,5 +1,5 @@ from datetime import datetime -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel class UserCertificate(BaseModel): @@ -9,6 +9,4 @@ class UserCertificate(BaseModel): quiz_id: int quiz_name: str completion_date: datetime - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/schemas/user_x_role.py b/training/schemas/user_x_role.py index defa523f..633d7895 100644 --- a/training/schemas/user_x_role.py +++ b/training/schemas/user_x_role.py @@ -1,9 +1,7 @@ -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel class UserXRole(BaseModel): user_id: int role_id: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/training/services/quiz.py b/training/services/quiz.py index 0279755e..7d48d434 100644 --- a/training/services/quiz.py +++ b/training/services/quiz.py @@ -14,7 +14,7 @@ def grade(self, quiz_id: int, user_id: int, submission: QuizSubmission) -> QuizG if db_quiz is None: raise QuizNotFoundError - quiz = Quiz.from_orm(db_quiz) + quiz = Quiz.model_validate(db_quiz) correct_count = 0 question_count = len(quiz.content.questions) questions = [] diff --git a/training/tests/conftest.py b/training/tests/conftest.py index a7d583bc..b7d39ba5 100644 --- a/training/tests/conftest.py +++ b/training/tests/conftest.py @@ -1,7 +1,7 @@ from collections.abc import Generator from unittest.mock import MagicMock import jwt -from pydantic import parse_obj_as +from pydantic import TypeAdapter import pytest import yaml import pathlib @@ -17,6 +17,8 @@ from . import factories from training.main import app +quiz_submission_adapter = TypeAdapter(schemas.QuizSubmission) + @pytest.fixture def db(): @@ -155,7 +157,7 @@ def valid_jwt(db_with_data: Session) -> str: Provides a JWT based on a test user in the database. ''' db_user = db_with_data.query(models.User).first() - user = schemas.UserJWT.from_orm(db_user).dict() + user = schemas.UserJWT.model_validate(db_user).model_dump() return jwt.encode(user, settings.JWT_SECRET, algorithm="HS256") @@ -221,7 +223,7 @@ def valid_passing_submission(testdata: dict) -> Generator[schemas.QuizSubmission Provides a QuizSubmission schema object containing valid passing responses. ''' jsondata = testdata["quiz_submissions"]["valid_passing"] - yield parse_obj_as(schemas.QuizSubmission, jsondata) + yield quiz_submission_adapter.validate_python(jsondata) @pytest.fixture @@ -230,7 +232,7 @@ def valid_failing_submission(testdata: dict) -> Generator[schemas.QuizSubmission Provides a QuizSubmission schema object containing valid failing responses. ''' jsondata = testdata["quiz_submissions"]["valid_failing"] - yield parse_obj_as(schemas.QuizSubmission, jsondata) + yield quiz_submission_adapter.validate_python(jsondata) @pytest.fixture @@ -240,7 +242,7 @@ def invalid_submission(testdata: dict) -> Generator[schemas.QuizSubmission, None responses. ''' jsondata = testdata["quiz_submissions"]["invalid_incomplete"] - yield parse_obj_as(schemas.QuizSubmission, jsondata) + yield quiz_submission_adapter.validate_python(jsondata) @pytest.fixture diff --git a/training/tests/test_api_agencies.py b/training/tests/test_api_agencies.py index 11b94976..0063f315 100644 --- a/training/tests/test_api_agencies.py +++ b/training/tests/test_api_agencies.py @@ -15,7 +15,7 @@ def test_create_agency(mock_agency_repo: AgencyRepository): mock_agency_repo.create.return_value = AgencySchemaFactory.build() response = client.post( "/api/v1/agencies", - json=agency_create.dict() + json=agency_create.model_dump() ) assert response.status_code == status.HTTP_201_CREATED @@ -26,7 +26,7 @@ def test_create_agency_duplicate(mock_agency_repo: AgencyRepository): mock_agency_repo.find_by_name.return_value = agency response = client.post( "/api/v1/agencies", - json=agency_create.dict() + json=agency_create.model_dump() ) assert response.status_code == status.HTTP_400_BAD_REQUEST diff --git a/training/tests/test_api_loginless_flow.py b/training/tests/test_api_loginless_flow.py index 2805b7eb..92b254bf 100644 --- a/training/tests/test_api_loginless_flow.py +++ b/training/tests/test_api_loginless_flow.py @@ -1,5 +1,4 @@ import pytest -from pydantic import EmailStr from unittest.mock import MagicMock, patch from fastapi.testclient import TestClient from training.main import app @@ -57,7 +56,7 @@ def authorized_complete(): return User( id=100, name="Stephen Dedalus", - email=EmailStr("test@example.com"), + email="test@example.com", agency_id=3, agency=Agency(id=3, name='test name', bureau="test"), roles=[Role(id=1, name='Wizard')], diff --git a/training/tests/test_api_quizzes.py b/training/tests/test_api_quizzes.py index cb721751..0e4a5360 100644 --- a/training/tests/test_api_quizzes.py +++ b/training/tests/test_api_quizzes.py @@ -20,7 +20,7 @@ def test_create_quiz_valid( mock_quiz_repo.create.return_value = QuizSchemaFactory.build() response = client.post( "/api/v1/quizzes", - json=valid_quiz_create.dict() + json=valid_quiz_create.model_dump() ) assert response.status_code == status.HTTP_201_CREATED @@ -31,7 +31,7 @@ def test_create_quiz_invalid(): quiz_create.audience = "Invalid" # type: ignore response = client.post( "/api/v1/quizzes", - json=quiz_create.dict() + json=quiz_create.model_dump() ) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY @@ -77,7 +77,7 @@ def test_submit_quiz(mock_quiz_service: QuizService, valid_jwt: str): mock_quiz_service.grade.return_value = QuizGradeSchemaFactory.build() response = client.post( "/api/v1/quizzes/1/submission", - json=QuizSubmissionSchemaFactory.build().dict(), + json=QuizSubmissionSchemaFactory.build().model_dump(), headers={"Authorization": f"Bearer {valid_jwt}"} ) assert response.status_code == status.HTTP_201_CREATED @@ -87,7 +87,7 @@ def test_submit_quiz_invalid_id(mock_quiz_service: QuizService, valid_jwt: str): mock_quiz_service.grade.side_effect = QuizNotFoundError response = client.post( "/api/v1/quizzes/1/submission", - json=QuizSubmissionSchemaFactory.build().dict(), + json=QuizSubmissionSchemaFactory.build().model_dump(), headers={"Authorization": f"Bearer {valid_jwt}"} ) assert response.status_code == status.HTTP_404_NOT_FOUND @@ -97,7 +97,7 @@ def test_submit_quiz_incomplete(mock_quiz_service: QuizService, valid_jwt: str): mock_quiz_service.grade.side_effect = IncompleteQuizResponseError([0, 1, 2]) response = client.post( "/api/v1/quizzes/1/submission", - json=QuizSubmissionSchemaFactory.build().dict(), + json=QuizSubmissionSchemaFactory.build().model_dump(), headers={"Authorization": f"Bearer {valid_jwt}"} ) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY diff --git a/training/tests/test_api_users.py b/training/tests/test_api_users.py index b5657304..0132b099 100644 --- a/training/tests/test_api_users.py +++ b/training/tests/test_api_users.py @@ -34,7 +34,7 @@ def test_create_user(goodJWT, mock_user_repo: UserRepository): mock_user_repo.create.return_value = UserSchemaFactory.build() response = client.post( "/api/v1/users", - json=user_create.dict(), + json=user_create.model_dump(), headers={"Authorization": f"Bearer {goodJWT}"} ) assert response.status_code == status.HTTP_201_CREATED @@ -46,7 +46,7 @@ def test_create_user_duplicate(goodJWT, mock_user_repo: UserRepository): mock_user_repo.find_by_email.return_value = user_create response = client.post( "/api/v1/users", - json=user_create.dict(), + json=user_create.model_dump(), headers={"Authorization": f"Bearer {goodJWT}"} ) assert response.status_code == status.HTTP_400_BAD_REQUEST @@ -88,7 +88,7 @@ def test_search_users_by_name(goodJWT, mock_user_repo: UserRepository): assert response.status_code == status.HTTP_200_OK assert len(response.json()) == 2 assert response.json()["total_count"] == 2 - assert response.json()["users"] == users + assert response.json()["users"] == [user.model_dump() for user in users] def test_edit_user_for_reporting(mock_user_repo: UserRepository, goodJWT: str): @@ -108,5 +108,5 @@ def test_edit_user_for_reporting(mock_user_repo: UserRepository, goodJWT: str): headers={"Authorization": f"Bearer {goodJWT}"} ) assert response.status_code == status.HTTP_200_OK - assert role in response.json()["roles"] - assert agency in response.json()["report_agencies"] + assert role.model_dump() in response.json()["roles"] + assert agency.model_dump() in response.json()["report_agencies"] diff --git a/training/tests/test_auth_login.py b/training/tests/test_auth_login.py index b24c7ece..a925664c 100644 --- a/training/tests/test_auth_login.py +++ b/training/tests/test_auth_login.py @@ -26,17 +26,17 @@ def regular_user() -> User: @pytest.fixture def admin_user_uaa_jwt(admin_user: User): - return jwt.encode(admin_user.dict(), "test_uaa_key", algorithm="HS256") + return jwt.encode(admin_user.model_dump(), "test_uaa_key", algorithm="HS256") @pytest.fixture def regular_user_uaa_jwt(regular_user: User): - return jwt.encode(regular_user.dict(), "test_uaa_key", algorithm="HS256") + return jwt.encode(regular_user.model_dump(), "test_uaa_key", algorithm="HS256") @pytest.fixture def invalid_jwt(admin_user: User): - payload = admin_user.dict() + payload = admin_user.model_dump() payload["aud"] = [settings.AUTH_CLIENT_ID, "openid"] return jwt.encode( payload, @@ -58,7 +58,7 @@ def test_auth_metadata(): @patch("training.repositories.UserRepository.find_by_email") @patch("training.api.auth.UAAJWTUser.decode_jwt") def test_auth_exchange_valid_jwt_admin_user(decode_jwt, find_by_email, admin_user, admin_user_uaa_jwt): - decode_jwt.return_value = admin_user.dict() + decode_jwt.return_value = admin_user.model_dump() find_by_email.return_value = admin_user response = client.post( @@ -71,7 +71,7 @@ def test_auth_exchange_valid_jwt_admin_user(decode_jwt, find_by_email, admin_use @patch("training.repositories.UserRepository.find_by_email") @patch("training.api.auth.UAAJWTUser.decode_jwt") def test_auth_exchange_valid_jwt_regular_user(decode_jwt, find_by_email, regular_user, regular_user_uaa_jwt): - decode_jwt.return_value = regular_user.dict() + decode_jwt.return_value = regular_user.model_dump() find_by_email.return_value = regular_user response = client.post( @@ -84,7 +84,7 @@ def test_auth_exchange_valid_jwt_regular_user(decode_jwt, find_by_email, regular @patch("training.repositories.UserRepository.find_by_email") @patch("training.api.auth.UAAJWTUser.decode_jwt") def test_auth_exchange_valid_jwt_nonexistent_user(decode_jwt, find_by_email, regular_user, regular_user_uaa_jwt): - decode_jwt.return_value = regular_user.dict() + decode_jwt.return_value = regular_user.model_dump() find_by_email.return_value = None response = client.post( diff --git a/training/tests/test_user_cache.py b/training/tests/test_user_cache.py index 906ad93b..f23a001f 100644 --- a/training/tests/test_user_cache.py +++ b/training/tests/test_user_cache.py @@ -2,7 +2,7 @@ import json from unittest import mock from training.data.user_cache import UserCache, redis -from training.schemas import TempUser +from training.schemas import TempUser, UserCreate @pytest.fixture @@ -22,7 +22,7 @@ def test_get_user(temp_user, uuid): u = UserCache() found_user = u.get(uuid) redis.get.assert_called_with(uuid) - assert found_user == TempUser(**temp_user) + assert found_user == UserCreate(**temp_user) def test_get_non_user(uuid):