From fb874fea3542675d8e4815c5c5fb16972fda03c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mocs=C3=A1r=20K=C3=A1lm=C3=A1n?= Date: Fri, 17 Apr 2020 09:20:00 +0200 Subject: [PATCH] :sparkles: Update CRUD utils for users handling password hashing (#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add some information how to run backand test for local backand development * Bug fixes in backend app * :art: Update format * :white_check_mark: Use random_email for test_update_user Co-authored-by: Mocsar Kalman Co-authored-by: Sebastián Ramírez --- {{cookiecutter.project_slug}}/README.md | 14 ++++++++++++ .../backend/app/app/crud/crud_user.py | 11 +++++++++- .../backend/app/app/schemas/user.py | 22 +++++++++---------- .../backend/app/app/tests/crud/test_user.py | 16 +++++++++++++- .../backend/app/app/tests/utils/user.py | 2 +- 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index abbb7cd..23b89b8 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -134,6 +134,20 @@ If you need to install any additional package for the tests, add it to the file If you use GitLab CI the tests will run automatically. +#### Local tests + +Start the stack with this command: + +```Bash +DOMAIN=backend sh ./scripts/test-local.sh +``` +The `./backend/app` directory is mounted as a "host volume" inside the docker container (set in the file `docker-compose.dev.volumes.yml`). +You can rerun the test on live code: + +```Bash +docker-compose exec backend-tests /tests-start.sh +``` + #### Test running stack If your stack is already up and you just want to run the tests, you can use: diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_user.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_user.py index e5d9d55..e8ade85 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_user.py @@ -3,7 +3,7 @@ from typing import Optional from sqlalchemy.orm import Session from app.models.user import User -from app.schemas.user import UserCreate, UserUpdate +from app.schemas.user import UserCreate, UserUpdate, UserInDB from app.core.security import verify_password, get_password_hash from app.crud.base import CRUDBase @@ -24,6 +24,15 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): db_session.refresh(db_obj) return db_obj + def update(self, db_session: Session, *, db_obj: User, obj_in: UserUpdate) -> User: + if obj_in.password: + update_data = obj_in.dict(exclude_unset=True) + hashed_password = get_password_hash(obj_in.password) + del update_data["password"] + update_data["hashed_password"] = hashed_password + use_obj_in = UserInDB.parse_obj(update_data) + return super().update(db_session, db_obj=db_obj, obj_in=use_obj_in) + def authenticate( self, db_session: Session, *, email: str, password: str ) -> Optional[User]: diff --git a/{{cookiecutter.project_slug}}/backend/app/app/schemas/user.py b/{{cookiecutter.project_slug}}/backend/app/app/schemas/user.py index d80e906..32e3a98 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/schemas/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/schemas/user.py @@ -11,29 +11,29 @@ class UserBase(BaseModel): full_name: Optional[str] = None -class UserBaseInDB(UserBase): - id: int = None - - class Config: - orm_mode = True - - # Properties to receive via API on creation -class UserCreate(UserBaseInDB): +class UserCreate(UserBase): email: EmailStr password: str # Properties to receive via API on update -class UserUpdate(UserBaseInDB): +class UserUpdate(UserBase): password: Optional[str] = None +class UserInDBBase(UserBase): + id: int = None + + class Config: + orm_mode = True + + # Additional properties to return via API -class User(UserBaseInDB): +class User(UserInDBBase): pass # Additional properties stored in DB -class UserInDB(UserBaseInDB): +class UserInDB(UserInDBBase): hashed_password: str diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py index 39cbf33..d1c1443 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py @@ -1,8 +1,9 @@ from fastapi.encoders import jsonable_encoder from app import crud +from app.core.security import get_password_hash, verify_password from app.db.session import db_session -from app.schemas.user import UserCreate +from app.schemas.user import UserCreate, UserUpdate from app.tests.utils.utils import random_lower_string, random_email @@ -78,3 +79,16 @@ def test_get_user(): user_2 = crud.user.get(db_session, id=user.id) assert user.email == user_2.email assert jsonable_encoder(user) == jsonable_encoder(user_2) + + +def test_update_user(): + password = random_lower_string() + email = random_email() + user_in = UserCreate(email=email, password=password, is_superuser=True) + user = crud.user.create(db_session, obj_in=user_in) + new_password = random_lower_string() + user_in = UserUpdate(password=new_password, is_superuser=True) + crud.user.update(db_session, db_obj=user, obj_in=user_in) + user_2 = crud.user.get(db_session, id=user.id) + assert user.email == user_2.email + assert verify_password(new_password, user_2.hashed_password) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py index 6dfa337..42f80ce 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py @@ -38,6 +38,6 @@ def authentication_token_from_email(email): user = crud.user.create(db_session=db_session, obj_in=user_in) else: user_in = UserUpdate(password=password) - user = crud.user.update(db_session, obj_in=user, db_obj=user_in) + user = crud.user.update(db_session, db_obj=user, obj_in=user_in) return user_authentication_headers(get_server_api(), email, password)