diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..863d9b3 --- /dev/null +++ b/app/database.py @@ -0,0 +1,24 @@ +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine +from sqlalchemy.orm import sessionmaker +from app.config import settings +from app.models import Base + +engine: AsyncEngine = create_async_engine(settings.database_url, echo=False) +AsyncSessionLocal = sessionmaker( + bind=engine, class_=AsyncSession, expire_on_commit=False +) + +async def init_db(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + +async def get_db(): + async with AsyncSessionLocal() as session: + try: + yield session + await session.commit() + except Exception: + await session.rollback() + raise + finally: + await session.close() diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..e1c7dad --- /dev/null +++ b/app/models.py @@ -0,0 +1,17 @@ +from datetime import datetime +from sqlalchemy import Column, Integer, String, Text, DateTime, Float +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + +class Contact(Base): + __tablename__ = "contacts" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String(255), nullable=False) + email = Column(String(255), nullable=False) + subject = Column(String(255), nullable=False) + message = Column(Text, nullable=False) + submitted_at = Column(DateTime, default=datetime.utcnow, nullable=False) + ip_address = Column(String(45), nullable=True) + recaptcha_score = Column(Float, nullable=True) diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..ac51bf6 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,17 @@ +import pytest +from app.config import Settings + +def test_settings_load_from_env(monkeypatch): + """Test that Settings loads from environment.""" + monkeypatch.setenv("DATABASE_URL", "mysql+aiomysql://user:pass@localhost/db") + monkeypatch.setenv("SMTP_HOST", "smtp.test.com") + monkeypatch.setenv("SMTP_PORT", "587") + monkeypatch.setenv("SMTP_USER", "test@test.com") + monkeypatch.setenv("SMTP_PASSWORD", "password") + monkeypatch.setenv("RECAPTCHA_SECRET", "secret") + monkeypatch.setenv("RECAPTCHA_SITE_KEY", "site_key") + monkeypatch.setenv("GITBUCKET_URL", "http://test.com/api") + + settings = Settings() + assert settings.database_url == "mysql+aiomysql://user:pass@localhost/db" + assert settings.smtp_host == "smtp.test.com" diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..95983e3 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,16 @@ +import pytest +from app.models import Contact + +def test_contact_model_columns(): + """Test that Contact model has required columns.""" + contact = Contact( + name="Test User", + email="test@example.com", + subject="Test Subject", + message="Test message" + ) + assert contact.name == "Test User" + assert contact.email == "test@example.com" + assert contact.subject == "Test Subject" + assert contact.message == "Test message" + assert contact.submitted_at is None # default not set yet