diff --git a/app/config.py b/app/config.py index 3616585..7cdf6bc 100644 --- a/app/config.py +++ b/app/config.py @@ -16,4 +16,6 @@ class Config: env_file = ".env" -settings = Settings() +def get_settings(): + """Return a Settings instance (lazy, creates on first call).""" + return Settings() diff --git a/app/crud.py b/app/crud.py new file mode 100644 index 0000000..31d7b45 --- /dev/null +++ b/app/crud.py @@ -0,0 +1,9 @@ +from app.models import Contact +from app.schemas import ContactCreate + +async def create_contact(db, contact_data: ContactCreate) -> Contact: + contact = Contact(**contact_data.dict()) + db.add(contact) + await db.commit() + await db.refresh(contact) + return contact diff --git a/app/schemas.py b/app/schemas.py new file mode 100644 index 0000000..bdebeef --- /dev/null +++ b/app/schemas.py @@ -0,0 +1,16 @@ +from pydantic import BaseModel, EmailStr, Field, field_validator + +class ContactCreate(BaseModel): + name: str = Field(..., min_length=1, max_length=255) + email: EmailStr + subject: str = Field(..., min_length=1, max_length=255) + message: str = Field(..., min_length=1) + + @field_validator('name', 'subject', 'message') + @classmethod + def strip_whitespace(cls, v): + return v.strip() if isinstance(v, str) else v + +class ContactResponse(BaseModel): + success: bool + message: str diff --git a/requirements.txt b/requirements.txt index 954eef8..0ee2bf7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,8 +4,10 @@ aiomysql==0.2.0 pydantic==2.5.0 pydantic-settings==2.1.0 +email-validator==2.1.0 aiosmtplib==3.0.1 httpx==0.25.1 pytest==7.4.3 pytest-asyncio==0.21.1 +pytest-mock==3.12.0 python-dotenv==1.0.0 diff --git a/tests/test_crud.py b/tests/test_crud.py new file mode 100644 index 0000000..a32280d --- /dev/null +++ b/tests/test_crud.py @@ -0,0 +1,26 @@ +import pytest +from app.crud import create_contact +from app.models import Contact +from app.schemas import ContactCreate +from unittest.mock import AsyncMock + +@pytest.mark.asyncio +async def test_create_contact(): + # Mock DB session + mock_session = AsyncMock() + mock_contact = Contact( + id=1, + name="Test", + email="test@example.com", + subject="Test", + message="Hello", + submitted_at=None + ) + mock_session.add = AsyncMock() + mock_session.commit = AsyncMock() + mock_session.refresh = AsyncMock() + + contact_data = ContactCreate(name="Test", email="test@example.com", subject="Test", message="Hello") + result = await create_contact(mock_session, contact_data) + mock_session.add.assert_called_once() + mock_session.commit.assert_called_once() diff --git a/tests/test_schemas.py b/tests/test_schemas.py new file mode 100644 index 0000000..0e8a43a --- /dev/null +++ b/tests/test_schemas.py @@ -0,0 +1,25 @@ +from app.schemas import ContactCreate + +def test_contact_create_valid(): + data = { + "name": "Jean Dupont", + "email": "jean@example.com", + "subject": "Test", + "message": "Hello" + } + contact = ContactCreate(**data) + assert contact.name == "Jean Dupont" + assert contact.email == "jean@example.com" + +def test_contact_create_invalid_email(): + data = { + "name": "Test", + "email": "not-an-email", + "subject": "Test", + "message": "Hello" + } + try: + ContactCreate(**data) + assert False, "Should raise validation error" + except Exception as e: + assert "email" in str(e).lower()