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
132 lines
3.3 KiB
Python
132 lines
3.3 KiB
Python
"""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 == []
|