wrongmove/tests/conftest.py

247 lines
7.3 KiB
Python
Raw Normal View History

"""Shared pytest fixtures for the test suite."""
from datetime import datetime
from typing import Any, AsyncGenerator, Callable, Generator
import pytest
import fakeredis
from sqlalchemy import Engine
from sqlmodel import SQLModel, Session, create_engine
from httpx import ASGITransport, AsyncClient
from models.listing import (
BuyListing,
FurnishType,
ListingSite,
RentListing,
Listing,
)
from repositories.listing_repository import ListingRepository
from api.auth import User
@pytest.fixture
def in_memory_engine() -> Generator[Engine, None, None]:
"""Create an in-memory SQLite engine for testing."""
engine = create_engine(
"sqlite:///:memory:",
echo=False,
connect_args={"check_same_thread": False},
)
SQLModel.metadata.create_all(engine)
yield engine
SQLModel.metadata.drop_all(engine)
@pytest.fixture
def listing_repository(in_memory_engine: Engine) -> ListingRepository:
"""Create a ListingRepository with the in-memory engine."""
return ListingRepository(engine=in_memory_engine)
@pytest.fixture
def sample_rent_listing() -> RentListing:
"""Create a sample RentListing for testing."""
return RentListing(
id=12345678,
price=2500.0,
number_of_bedrooms=2,
square_meters=65.0,
agency="Test Agency",
council_tax_band="C",
longitude=-0.1276,
latitude=51.5074,
price_history_json="[]",
listing_site=ListingSite.RIGHTMOVE,
last_seen=datetime.now(),
photo_thumbnail="https://example.com/photo.jpg",
floorplan_image_paths=[],
additional_info={"property": {"visible": True}},
routing_info_json=None,
furnish_type=FurnishType.FURNISHED,
available_from=datetime.now(),
)
@pytest.fixture
def sample_buy_listing() -> BuyListing:
"""Create a sample BuyListing for testing."""
return BuyListing(
id=87654321,
price=450000.0,
number_of_bedrooms=3,
square_meters=95.0,
agency="Test Estate Agents",
council_tax_band="D",
longitude=-0.1180,
latitude=51.5100,
price_history_json="[]",
listing_site=ListingSite.RIGHTMOVE,
last_seen=datetime.now(),
photo_thumbnail="https://example.com/buy_photo.jpg",
floorplan_image_paths=[],
additional_info={"property": {"visible": True}},
routing_info_json=None,
service_charge=1500.0,
lease_left=90,
)
@pytest.fixture
def sample_rent_listings() -> list[RentListing]:
"""Create multiple sample RentListings for testing filters."""
now = datetime.now()
return [
RentListing(
id=1,
price=1500.0,
number_of_bedrooms=1,
square_meters=40.0,
agency="Agency A",
council_tax_band="B",
longitude=-0.1,
latitude=51.5,
price_history_json="[]",
listing_site=ListingSite.RIGHTMOVE,
last_seen=now,
photo_thumbnail=None,
floorplan_image_paths=[],
additional_info={"property": {"visible": True}},
routing_info_json=None,
furnish_type=FurnishType.FURNISHED,
available_from=now,
),
RentListing(
id=2,
price=2000.0,
number_of_bedrooms=2,
square_meters=55.0,
agency="Agency B",
council_tax_band="C",
longitude=-0.12,
latitude=51.51,
price_history_json="[]",
listing_site=ListingSite.RIGHTMOVE,
last_seen=now,
photo_thumbnail=None,
floorplan_image_paths=[],
additional_info={"property": {"visible": True}},
routing_info_json=None,
furnish_type=FurnishType.UNFURNISHED,
available_from=now,
),
RentListing(
id=3,
price=3000.0,
number_of_bedrooms=3,
square_meters=80.0,
agency="Agency C",
council_tax_band="D",
longitude=-0.14,
latitude=51.52,
price_history_json="[]",
listing_site=ListingSite.RIGHTMOVE,
last_seen=now,
photo_thumbnail=None,
floorplan_image_paths=[],
additional_info={"property": {"visible": True}},
routing_info_json=None,
furnish_type=FurnishType.FURNISHED,
available_from=now,
),
]
@pytest.fixture
def mock_user() -> User:
"""Create a mock user for API tests."""
return User(
sub="test-user-id",
email="test@example.com",
name="Test User",
)
@pytest.fixture
async def async_client(
in_memory_engine: Engine, mock_user: User
) -> AsyncGenerator[AsyncClient, None]:
"""Create an AsyncClient for API testing with mock auth."""
import database
import api.app as api_app
from api.auth import get_current_user
app = api_app.app
# Override dependencies
app.dependency_overrides[get_current_user] = lambda: mock_user
# Patch the engine so the API uses the in-memory database
original_db_engine = database.engine
original_app_engine = api_app.engine
database.engine = in_memory_engine
api_app.engine = in_memory_engine
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
yield client
# Restore original engines and clean up
database.engine = original_db_engine
api_app.engine = original_app_engine
app.dependency_overrides.clear()
@pytest.fixture
def fake_redis() -> Generator[fakeredis.FakeRedis, None, None]:
"""Create a fakeredis client, flushed after each test."""
client = fakeredis.FakeRedis(decode_responses=True)
yield client
client.flushall()
@pytest.fixture
def rent_listing_factory() -> Callable[..., RentListing]:
"""Factory function that creates RentListing with overridable defaults."""
_counter = 0
def _create(**overrides: Any) -> RentListing:
nonlocal _counter
_counter += 1
defaults: dict[str, Any] = dict(
id=_counter,
price=2000.0,
number_of_bedrooms=2,
square_meters=55.0,
agency="Test Agency",
council_tax_band="C",
longitude=-0.1276,
latitude=51.5074,
price_history_json="[]",
listing_site=ListingSite.RIGHTMOVE,
last_seen=datetime.now(),
photo_thumbnail="https://example.com/photo.jpg",
floorplan_image_paths=[],
additional_info={"property": {"visible": True}},
routing_info_json=None,
furnish_type=FurnishType.FURNISHED,
available_from=datetime.now(),
)
defaults.update(overrides)
return RentListing(**defaults)
return _create
@pytest.fixture
async def seeded_repository(
in_memory_engine: Engine,
rent_listing_factory: Callable[..., RentListing],
) -> ListingRepository:
"""Repository with 10 pre-seeded listings (varied price/bedrooms/sqm)."""
repo = ListingRepository(engine=in_memory_engine)
listings = [
rent_listing_factory(id=100 + i, price=1000 + i * 300, number_of_bedrooms=(i % 4) + 1, square_meters=30 + i * 10)
for i in range(10)
]
await repo.upsert_listings(listings)
return repo