wrongmove/tests/conftest.py
Viktor Barzin 8d22c97320
Add comprehensive test suite: 219 new tests across backend and frontend
Backend (103 tests):
- Unit tests for listing_service, export_service, district_service
- Regression tests for API response contracts and query parameter validation
- Integration tests for API workflows, Redis listing cache, listing processor pipeline, and repository advanced queries
- E2E tests for streaming with filters, batching, caching, and task management

Frontend (116 tests):
- Service tests for apiClient, streamingService, taskService, listingService, healthService
- Hook tests for useTaskProgress (WebSocket + polling)
- Component tests for PropertyCard, FilterPanel, Header, ListView, TaskProgressDrawer, TaskIndicator, StreamingProgressBar, HealthIndicator
- E2E tests for filter-stream-display flow

Infrastructure:
- Add pytest-xdist and test markers (regression, integration, e2e)
- Add conftest fixtures: fake_redis, rent_listing_factory, seeded_repository
- Add vitest + testing-library + MSW for frontend testing
2026-02-10 21:59:45 +00:00

243 lines
7.2 KiB
Python

"""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."""
from api.app import app
from api.auth import get_current_user
# Override dependencies
app.dependency_overrides[get_current_user] = lambda: mock_user
# Patch the engine used by the repository
original_engine = None
try:
from database import engine as db_engine
original_engine = db_engine
except Exception:
pass
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
yield client
# Clean up dependency overrides
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