Spaces:
Sleeping
Sleeping
| """ | |
| Shared pytest fixtures for the Apl_GS test suite. | |
| Provides: | |
| - async_engine: in-memory SQLite engine for isolation | |
| - async_session: transactional session rolled back after each test | |
| - sample_visit / sample_checklist / sample_photo / sample_outlet / sample_assessment | |
| """ | |
| import asyncio | |
| from datetime import datetime, timezone | |
| from typing import AsyncGenerator | |
| import pytest | |
| import pytest_asyncio | |
| from sqlalchemy.ext.asyncio import ( | |
| AsyncSession, | |
| async_sessionmaker, | |
| create_async_engine, | |
| ) | |
| # Import all models so SQLAlchemy registers them before create_all | |
| from models.base import Base | |
| import models.visit_model # noqa: F401 | |
| import models.checklist_model # noqa: F401 | |
| import models.photo_model # noqa: F401 | |
| import models.assessment_model # noqa: F401 | |
| import models.outlet_model # noqa: F401 | |
| from models.visit_model import Visit | |
| from models.checklist_model import Checklist | |
| from models.photo_model import Photo | |
| from models.assessment_model import Assessment | |
| from models.outlet_model import Outlet | |
| # ββ In-memory async SQLite engine βββββββββββββββββββββββββββββββββββββββββ | |
| TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:" | |
| def event_loop(): | |
| """Use a single event loop for the whole session.""" | |
| loop = asyncio.new_event_loop() | |
| yield loop | |
| loop.close() | |
| async def async_engine(): | |
| """Create engine + tables once per session.""" | |
| engine = create_async_engine( | |
| TEST_DATABASE_URL, | |
| connect_args={"check_same_thread": False}, | |
| ) | |
| async with engine.begin() as conn: | |
| await conn.run_sync(Base.metadata.create_all) | |
| yield engine | |
| async with engine.begin() as conn: | |
| await conn.run_sync(Base.metadata.drop_all) | |
| await engine.dispose() | |
| async def async_session(async_engine) -> AsyncGenerator[AsyncSession, None]: | |
| """Provide a transactional session that rolls back after each test.""" | |
| session_factory = async_sessionmaker( | |
| async_engine, expire_on_commit=False | |
| ) | |
| async with session_factory() as session: | |
| async with session.begin(): | |
| yield session | |
| await session.rollback() | |
| # ββ Sample data factories ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def sample_outlet(async_session: AsyncSession) -> Outlet: | |
| """Persist and return a sample Outlet.""" | |
| outlet = Outlet( | |
| name="Outlet Gedung A", | |
| location="Jl. Sudirman No. 1", | |
| description="Outlet pusat", | |
| ) | |
| async_session.add(outlet) | |
| await async_session.flush() | |
| return outlet | |
| async def sample_visit(async_session: AsyncSession, sample_outlet: Outlet) -> Visit: | |
| """Persist and return a sample Visit linked to sample_outlet.""" | |
| visit = Visit( | |
| title="Test Site Inspection", | |
| description="Monthly check", | |
| location="Gedung A", | |
| visit_date=datetime(2026, 5, 1, 9, 0, tzinfo=timezone.utc), | |
| status="pending", | |
| outlet_id=sample_outlet.id, | |
| ghost_shopper="Auditor Test", | |
| visit_type="dine_in", | |
| ) | |
| async_session.add(visit) | |
| await async_session.flush() | |
| return visit | |
| async def sample_checklist( | |
| async_session: AsyncSession, sample_visit: Visit | |
| ) -> Checklist: | |
| """Persist and return a sample Checklist item linked to sample_visit.""" | |
| item = Checklist( | |
| visit_id=sample_visit.id, | |
| item_name="Panel listrik", | |
| is_checked=False, | |
| ) | |
| async_session.add(item) | |
| await async_session.flush() | |
| return item | |
| async def sample_photo( | |
| async_session: AsyncSession, sample_visit: Visit | |
| ) -> Photo: | |
| """Persist and return a sample Photo linked to sample_visit.""" | |
| photo = Photo( | |
| visit_id=sample_visit.id, | |
| file_path="/uploads/test.jpg", | |
| file_name="test.jpg", | |
| file_size=102400, | |
| mime_type="image/jpeg", | |
| ) | |
| async_session.add(photo) | |
| await async_session.flush() | |
| return photo | |
| async def sample_assessment( | |
| async_session: AsyncSession, sample_visit: Visit | |
| ) -> Assessment: | |
| """Persist and return a sample Assessment item linked to sample_visit.""" | |
| assessment = Assessment( | |
| visit_id=sample_visit.id, | |
| category="A", | |
| item_no=1, | |
| criteria="Welcoming Kasir (Senyum, Sapa, Salam)", | |
| score=4.0, | |
| raw_value="4", | |
| notes="Baik", | |
| ) | |
| async_session.add(assessment) | |
| await async_session.flush() | |
| return assessment | |