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
This commit is contained in:
parent
a3ac9cc060
commit
8d22c97320
36 changed files with 5447 additions and 19 deletions
132
tests/integration/test_listing_cache.py
Normal file
132
tests/integration/test_listing_cache.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"""Integration tests for Redis-based listing cache."""
|
||||
import pytest
|
||||
|
||||
from models.listing import ListingType, QueryParameters
|
||||
from services.listing_cache import (
|
||||
begin_cache_population,
|
||||
cache_features_batch,
|
||||
cache_features_batch_staged,
|
||||
delete_staging_key,
|
||||
finalize_cache_population,
|
||||
get_cached_count,
|
||||
get_cached_features,
|
||||
invalidate_cache,
|
||||
make_cache_key,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_redis(fake_redis, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Route all cache operations through fakeredis."""
|
||||
monkeypatch.setattr("services.listing_cache._get_redis_client", lambda: fake_redis)
|
||||
|
||||
|
||||
def _make_qp(**kwargs) -> QueryParameters:
|
||||
return QueryParameters(listing_type=ListingType.RENT, **kwargs)
|
||||
|
||||
|
||||
def _sample_features(n: int) -> list[dict]:
|
||||
return [{"type": "Feature", "id": i, "properties": {"price": 1000 + i}} for i in range(n)]
|
||||
|
||||
|
||||
# ---------- Basic read/write ----------
|
||||
|
||||
|
||||
def test_cache_miss_returns_none() -> None:
|
||||
qp = _make_qp()
|
||||
assert get_cached_count(qp) is None
|
||||
|
||||
|
||||
def test_cache_write_then_read() -> None:
|
||||
qp = _make_qp()
|
||||
features = _sample_features(5)
|
||||
cache_features_batch(qp, features)
|
||||
|
||||
count = get_cached_count(qp)
|
||||
assert count == 5
|
||||
|
||||
|
||||
def test_batch_retrieval() -> None:
|
||||
qp = _make_qp()
|
||||
cache_features_batch(qp, _sample_features(10))
|
||||
|
||||
batches = list(get_cached_features(qp, batch_size=3))
|
||||
sizes = [len(b) for b in batches]
|
||||
assert sizes == [3, 3, 3, 1]
|
||||
|
||||
|
||||
# ---------- Cache key behaviour ----------
|
||||
|
||||
|
||||
def test_cache_key_deterministic() -> None:
|
||||
qp1 = _make_qp()
|
||||
qp2 = _make_qp()
|
||||
assert make_cache_key(qp1) == make_cache_key(qp2)
|
||||
|
||||
|
||||
def test_cache_key_different_for_different_params() -> None:
|
||||
rent = _make_qp()
|
||||
buy = QueryParameters(listing_type=ListingType.BUY)
|
||||
assert make_cache_key(rent) != make_cache_key(buy)
|
||||
|
||||
|
||||
# ---------- Staged population ----------
|
||||
|
||||
|
||||
def test_staged_population_begin() -> None:
|
||||
qp = _make_qp()
|
||||
staging_key = begin_cache_population(qp)
|
||||
assert isinstance(staging_key, str)
|
||||
assert "staging" in staging_key
|
||||
|
||||
|
||||
def test_staged_write_then_finalize() -> None:
|
||||
qp = _make_qp()
|
||||
staging_key = begin_cache_population(qp)
|
||||
cache_features_batch_staged(staging_key, _sample_features(4))
|
||||
finalize_cache_population(staging_key, qp)
|
||||
|
||||
assert get_cached_count(qp) == 4
|
||||
|
||||
|
||||
def test_staging_key_deleted_on_cleanup(fake_redis) -> None:
|
||||
qp = _make_qp()
|
||||
staging_key = begin_cache_population(qp)
|
||||
cache_features_batch_staged(staging_key, _sample_features(2))
|
||||
delete_staging_key(staging_key)
|
||||
|
||||
assert fake_redis.exists(staging_key) == 0
|
||||
|
||||
|
||||
# ---------- Invalidation ----------
|
||||
|
||||
|
||||
def test_invalidation() -> None:
|
||||
qp = _make_qp()
|
||||
cache_features_batch(qp, _sample_features(5))
|
||||
assert get_cached_count(qp) == 5
|
||||
|
||||
invalidate_cache()
|
||||
assert get_cached_count(qp) is None
|
||||
|
||||
|
||||
# ---------- Edge cases ----------
|
||||
|
||||
|
||||
def test_empty_features_batch_noop() -> None:
|
||||
qp = _make_qp()
|
||||
cache_features_batch(qp, [])
|
||||
assert get_cached_count(qp) is None
|
||||
|
||||
|
||||
def test_multiple_batches_accumulate() -> None:
|
||||
qp = _make_qp()
|
||||
cache_features_batch(qp, _sample_features(3))
|
||||
cache_features_batch(qp, _sample_features(4))
|
||||
assert get_cached_count(qp) == 7
|
||||
|
||||
|
||||
def test_get_cached_features_empty() -> None:
|
||||
qp = _make_qp()
|
||||
batches = list(get_cached_features(qp))
|
||||
assert batches == []
|
||||
Loading…
Add table
Add a link
Reference in a new issue