feat: filter disliked listings from GeoJSON endpoints

Both /api/listing_geojson and /api/listing_geojson/stream now exclude
disliked listings by default. A decision_filter='everything' param
bypasses filtering. 2 integration tests verify the behavior.
This commit is contained in:
Viktor Barzin 2026-02-21 13:57:43 +00:00
parent 43084ef19a
commit 8452f65d25
No known key found for this signature in database
GPG key ID: 0EB088298288D958
2 changed files with 165 additions and 2 deletions

View file

@ -0,0 +1,121 @@
"""Test that disliked listings are filtered from the GeoJSON endpoint."""
import pytest
from datetime import datetime
from httpx import ASGITransport, AsyncClient
from sqlalchemy import Engine
from sqlmodel import SQLModel, Session, create_engine
from models.user import User
from models.listing import RentListing, ListingSite, FurnishType
from models.decision import ListingDecision
from api.auth import get_current_user, User as AuthUser
@pytest.fixture
def filter_engine() -> Engine:
engine = create_engine(
"sqlite:///:memory:",
echo=False,
connect_args={"check_same_thread": False},
)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(User(id=1, email="test@example.com"))
# Add two listings
now = datetime.now()
for lid in [100, 200]:
session.add(RentListing(
id=lid,
price=2000.0,
number_of_bedrooms=2,
square_meters=50.0,
longitude=-0.1,
latitude=51.5,
price_history_json="[]",
listing_site=ListingSite.RIGHTMOVE,
last_seen=now,
floorplan_image_paths=[],
additional_info={"property": {"visible": True}},
furnish_type=FurnishType.FURNISHED,
))
# Dislike listing 200
session.add(ListingDecision(
user_id=1,
listing_id=200,
listing_type="RENT",
decision="disliked",
))
session.commit()
yield engine # type: ignore[misc]
SQLModel.metadata.drop_all(engine)
@pytest.fixture
async def filter_client(filter_engine: Engine) -> AsyncClient:
import database
import api.app as api_app
import api.decision_routes as decision_routes_mod
import api.poi_routes as poi_routes_mod
app = api_app.app
mock_user = AuthUser(
sub="test", email="test@example.com", name="Test"
)
app.dependency_overrides[get_current_user] = lambda: mock_user
original_db = database.engine
original_app = api_app.engine
original_decision = decision_routes_mod.engine
original_poi = poi_routes_mod.engine
database.engine = filter_engine
api_app.engine = filter_engine
decision_routes_mod.engine = filter_engine
poi_routes_mod.engine = filter_engine
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as c:
yield c # type: ignore[misc]
database.engine = original_db
api_app.engine = original_app
decision_routes_mod.engine = original_decision
poi_routes_mod.engine = original_poi
app.dependency_overrides.clear()
class TestDecisionFiltering:
@pytest.mark.asyncio
async def test_disliked_excluded_by_default(
self, filter_client: AsyncClient
) -> None:
"""Default decision_filter should exclude disliked listings."""
resp = await filter_client.get(
"/api/listing_geojson",
params={"listing_type": "RENT"},
)
assert resp.status_code == 200
data = resp.json()
listing_ids = [
f["properties"]["url"].split("/")[-1]
for f in data["features"]
]
assert "100" in listing_ids
assert "200" not in listing_ids
@pytest.mark.asyncio
async def test_everything_filter_includes_all(
self, filter_client: AsyncClient
) -> None:
"""decision_filter='everything' should include disliked listings."""
resp = await filter_client.get(
"/api/listing_geojson",
params={"listing_type": "RENT", "decision_filter": "everything"},
)
assert resp.status_code == 200
data = resp.json()
listing_ids = [
f["properties"]["url"].split("/")[-1]
for f in data["features"]
]
assert "100" in listing_ids
assert "200" in listing_ids