-
Notifications
You must be signed in to change notification settings - Fork 264
Solution #262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Solution #262
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,11 +4,15 @@ services: | |
| context: . | ||
| dockerfile: ./docker/tests/Dockerfile | ||
| container_name: backend_theater_test | ||
| command: [ "pytest", "-c", "/usr/src/config/pytest.ini", | ||
| "-m", "e2e", "--maxfail=5", "--disable-warnings", "-v", "--tb=short"] | ||
| command: > | ||
| sh -c " | ||
| alembic upgrade head && | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You run |
||
| pytest -c /usr/src/config/pytest.ini -m e2e --maxfail=5 --disable-warnings -v --tb=short | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The pytest command uses |
||
| " | ||
| environment: | ||
| - PYTHONPATH=/usr/src/fastapi | ||
| - ENVIRONMENT=testing | ||
| - ALEMBIC_CONFIG=/usr/src/config/alembic.ini | ||
| - EMAIL_HOST=mailhog_theater_test | ||
| - EMAIL_PORT=1025 | ||
| - EMAIL_HOST_USER=testuser@mate.com | ||
|
|
@@ -27,6 +31,7 @@ services: | |
| condition: service_healthy | ||
| volumes: | ||
| - ./src:/usr/src/fastapi | ||
| - ./alembic.ini:/usr/src/config/alembic.ini | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mount |
||
| networks: | ||
| - theater_network_test | ||
|
|
||
|
|
||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -103,3 +103,4 @@ def get_s3_storage_client( | |
| secret_key=settings.S3_STORAGE_SECRET_KEY, | ||
| bucket_name=settings.S3_BUCKET_NAME | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,11 @@ | ||
| from logging.config import fileConfig | ||
|
|
||
| from alembic import context | ||
| from sqlalchemy import create_engine | ||
|
|
||
| from config import get_settings | ||
| from database.models import movies, accounts # noqa: F401 | ||
| from database.models.base import Base | ||
| from database.session_postgresql import sync_postgresql_engine | ||
|
|
||
|
|
||
| # this is the Alembic Config object, which provides | ||
|
|
@@ -26,7 +27,10 @@ | |
| # can be acquired: | ||
| # my_important_option = config.get_main_option("my_important_option") | ||
| # ... etc. | ||
| settings = get_settings() | ||
| DATABASE_URL = settings.DATABASE_URL.replace("+aiosqlite", "") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line will raise an |
||
|
|
||
| connectable = create_engine(DATABASE_URL) | ||
|
|
||
| def run_migrations_offline() -> None: | ||
| """Run migrations in 'offline' mode. | ||
|
|
@@ -40,18 +44,17 @@ def run_migrations_offline() -> None: | |
| script output. | ||
|
|
||
| """ | ||
| connectable = sync_postgresql_engine | ||
|
|
||
| with connectable.connect() as connection: | ||
| context.configure( | ||
| connection=connection, | ||
| target_metadata=target_metadata, | ||
| compare_type=True, | ||
| compare_server_default=True | ||
| ) | ||
| context.configure( | ||
| url=DATABASE_URL, | ||
| target_metadata=target_metadata, | ||
| literal_binds=True, | ||
| compare_type=True, | ||
| compare_server_default=True | ||
| ) | ||
|
|
||
| with context.begin_transaction(): | ||
| context.run_migrations() | ||
| with context.begin_transaction(): | ||
| context.run_migrations() | ||
|
|
||
|
|
||
| def run_migrations_online() -> None: | ||
|
|
@@ -61,7 +64,6 @@ def run_migrations_online() -> None: | |
| and associate a connection with the context. | ||
|
|
||
| """ | ||
| connectable = sync_postgresql_engine | ||
|
|
||
| with connectable.connect() as connection: | ||
| context.configure( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| from datetime import datetime, timezone | ||
| from typing import cast | ||
|
|
||
| from fastapi import APIRouter, Depends, status, HTTPException | ||
| from fastapi import APIRouter, Depends, status, HTTPException, BackgroundTasks | ||
|
|
||
| from sqlalchemy import select, delete | ||
| from sqlalchemy.exc import SQLAlchemyError | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
|
|
@@ -67,7 +68,9 @@ | |
| ) | ||
| async def register_user( | ||
| user_data: UserRegistrationRequestSchema, | ||
| background_tasks: BackgroundTasks, | ||
| db: AsyncSession = Depends(get_db), | ||
| email_sender: EmailSenderInterface = Depends(get_accounts_email_notificator) | ||
| ) -> UserRegistrationResponseSchema: | ||
| """ | ||
| Endpoint for user registration. | ||
|
|
@@ -101,10 +104,10 @@ async def register_user( | |
| result = await db.execute(stmt) | ||
| user_group = result.scalars().first() | ||
| if not user_group: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail="Default user group not found." | ||
| ) | ||
| user_group = UserGroupModel(name=UserGroupEnum.USER) | ||
| db.add(user_group) | ||
| await db.commit() | ||
| await db.refresh(user_group) | ||
|
|
||
| try: | ||
| new_user = UserModel.create( | ||
|
|
@@ -120,6 +123,13 @@ async def register_user( | |
|
|
||
| await db.commit() | ||
| await db.refresh(new_user) | ||
| activation_link = f"http://127.0.0.1/api/v1/accounts/activate/?token={activation_token.token}&email={user_data.email}" | ||
|
|
||
| background_tasks.add_task( | ||
| email_sender.send_activation_email, | ||
| new_user.email, | ||
| activation_link, | ||
| ) | ||
| except SQLAlchemyError as e: | ||
| await db.rollback() | ||
| raise HTTPException( | ||
|
|
@@ -163,7 +173,9 @@ async def register_user( | |
| ) | ||
| async def activate_account( | ||
| activation_data: UserActivationRequestSchema, | ||
| background_tasks: BackgroundTasks, | ||
| db: AsyncSession = Depends(get_db), | ||
| email_sender: EmailSenderInterface = Depends(get_accounts_email_notificator), | ||
| ) -> MessageResponseSchema: | ||
| """ | ||
| Endpoint to activate a user's account. | ||
|
|
@@ -217,6 +229,14 @@ async def activate_account( | |
| user.is_active = True | ||
| await db.delete(token_record) | ||
| await db.commit() | ||
| await db.refresh(user) | ||
|
|
||
| login_link = "http://127.0.0.1/login/" | ||
| background_tasks.add_task( | ||
| email_sender.send_activation_complete_email, | ||
| user.email, | ||
| login_link | ||
| ) | ||
|
|
||
| return MessageResponseSchema(message="User account activated successfully.") | ||
|
|
||
|
|
@@ -233,7 +253,9 @@ async def activate_account( | |
| ) | ||
| async def request_password_reset_token( | ||
| data: PasswordResetRequestSchema, | ||
| background_tasks: BackgroundTasks, | ||
| db: AsyncSession = Depends(get_db), | ||
| email_sender: EmailSenderInterface = Depends(get_accounts_email_notificator), | ||
| ) -> MessageResponseSchema: | ||
| """ | ||
| Endpoint to request a password reset token. | ||
|
|
@@ -262,6 +284,12 @@ async def request_password_reset_token( | |
| reset_token = PasswordResetTokenModel(user_id=cast(int, user.id)) | ||
| db.add(reset_token) | ||
| await db.commit() | ||
| reset_link = f"http://127.0.0.1/accounts/reset-password/complete/?token={reset_token.token}&email={user.email}" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| background_tasks.add_task( | ||
| email_sender.send_password_reset_email, | ||
| user.email, | ||
| reset_link | ||
| ) | ||
|
|
||
| return MessageResponseSchema( | ||
| message="If you are registered, you will receive an email with instructions." | ||
|
|
@@ -313,7 +341,10 @@ async def request_password_reset_token( | |
| ) | ||
| async def reset_password( | ||
| data: PasswordResetCompleteRequestSchema, | ||
| background_tasks: BackgroundTasks, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| db: AsyncSession = Depends(get_db), | ||
| email_sender: EmailSenderInterface = Depends(get_accounts_email_notificator), | ||
|
|
||
| ) -> MessageResponseSchema: | ||
| """ | ||
| Endpoint for resetting a user's password. | ||
|
|
@@ -360,6 +391,7 @@ async def reset_password( | |
| if expires_at < datetime.now(timezone.utc): | ||
| await db.run_sync(lambda s: s.delete(token_record)) | ||
| await db.commit() | ||
|
|
||
| raise HTTPException( | ||
| status_code=status.HTTP_400_BAD_REQUEST, | ||
| detail="Invalid email or token." | ||
|
|
@@ -369,6 +401,13 @@ async def reset_password( | |
| user.password = data.password | ||
| await db.run_sync(lambda s: s.delete(token_record)) | ||
| await db.commit() | ||
|
|
||
| login_link = "http://127.0.0.1/accounts/login/" | ||
| background_tasks.add_task( | ||
| email_sender.send_password_reset_complete_email, | ||
| user.email, | ||
| login_link | ||
| ) | ||
| except SQLAlchemyError: | ||
| await db.rollback() | ||
| raise HTTPException( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command is executed via
sh -c " ... ". This is fine, but confirm the working directory and mounted files inside the test image so both Alembic and pytest find their configs and the application code as expected.