185 lines
6.0 KiB
Python
185 lines
6.0 KiB
Python
"""Tests for API endpoints."""
|
|
|
|
from datetime import datetime
|
|
from unittest.mock import MagicMock
|
|
|
|
from app.models import EloRating
|
|
|
|
|
|
def _fake_listing(**overrides) -> dict:
|
|
"""Return a mock DB row that looks like a joined listing+rating row."""
|
|
defaults = dict(
|
|
global_id="abc-123",
|
|
tiny_id="123",
|
|
url="https://example.com/listing",
|
|
title="Nice House",
|
|
city="Amsterdam",
|
|
postcode="1012AB",
|
|
province="Noord-Holland",
|
|
neighbourhood="Centrum",
|
|
municipality="Amsterdam",
|
|
latitude=52.37,
|
|
longitude=4.89,
|
|
object_type="apartment",
|
|
house_type="upstairs",
|
|
offering_type="buy",
|
|
construction_type="existing",
|
|
construction_year="2000",
|
|
energy_label="A",
|
|
living_area=80,
|
|
plot_area=0,
|
|
bedrooms=2,
|
|
rooms=4,
|
|
has_garden=False,
|
|
has_balcony=True,
|
|
has_solar_panels=False,
|
|
has_heat_pump=False,
|
|
has_roof_terrace=False,
|
|
is_energy_efficient=True,
|
|
is_monument=False,
|
|
current_price=350000,
|
|
status="available",
|
|
price_per_sqm=4375.0,
|
|
publication_date="2024-01-15",
|
|
elo_rating=1500.0,
|
|
comparison_count=0,
|
|
wins=0,
|
|
losses=0,
|
|
)
|
|
defaults.update(overrides)
|
|
row = MagicMock()
|
|
for k, v in defaults.items():
|
|
setattr(row, k, v)
|
|
return row
|
|
|
|
|
|
class TestHealthEndpoint:
|
|
def test_health(self, client):
|
|
resp = client.get("/api/health")
|
|
assert resp.status_code == 200
|
|
assert resp.json() == {"status": "ok"}
|
|
|
|
|
|
class TestListingsEndpoints:
|
|
def test_get_listings(self, client, db_session):
|
|
row = _fake_listing()
|
|
db_session.execute.return_value = [row]
|
|
resp = client.get("/api/listings")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list)
|
|
assert len(data) == 1
|
|
assert data[0]["global_id"] == "abc-123"
|
|
|
|
def test_get_listing_not_found(self, client, db_session):
|
|
result_mock = MagicMock()
|
|
result_mock.first.return_value = None
|
|
db_session.execute.return_value = result_mock
|
|
resp = client.get("/api/listings/nonexistent")
|
|
assert resp.status_code == 404
|
|
|
|
def test_get_listing_found(self, client, db_session):
|
|
row = _fake_listing(global_id="xyz-789")
|
|
result_mock = MagicMock()
|
|
result_mock.first.return_value = row
|
|
db_session.execute.return_value = result_mock
|
|
resp = client.get("/api/listings/xyz-789")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["global_id"] == "xyz-789"
|
|
|
|
|
|
class TestRankingsEndpoints:
|
|
def test_get_rankings(self, client, db_session):
|
|
rows = [
|
|
_fake_listing(global_id="a", elo_rating=1600.0),
|
|
_fake_listing(global_id="b", elo_rating=1400.0),
|
|
]
|
|
db_session.execute.return_value = rows
|
|
resp = client.get("/api/rankings")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 2
|
|
assert data[0]["rank"] == 1
|
|
assert data[1]["rank"] == 2
|
|
|
|
|
|
class TestImagesEndpoint:
|
|
def test_images_found(self, client, db_session):
|
|
row = MagicMock()
|
|
row.photo_urls = ["https://img.example.com/1.jpg", "https://img.example.com/2.jpg"]
|
|
result_mock = MagicMock()
|
|
result_mock.first.return_value = row
|
|
db_session.execute.return_value = result_mock
|
|
resp = client.get("/api/listings/abc-123/images")
|
|
assert resp.status_code == 200
|
|
assert len(resp.json()["images"]) == 2
|
|
|
|
def test_images_not_found(self, client, db_session):
|
|
result_mock = MagicMock()
|
|
result_mock.first.return_value = None
|
|
db_session.execute.return_value = result_mock
|
|
resp = client.get("/api/listings/abc-123/images")
|
|
assert resp.status_code == 200
|
|
assert resp.json() == {"images": []}
|
|
|
|
|
|
class TestCompareEndpoints:
|
|
def test_compare_same_ids_rejected(self, client):
|
|
resp = client.post(
|
|
"/api/compare",
|
|
json={"winner_id": "abc", "loser_id": "abc"},
|
|
)
|
|
assert resp.status_code == 400
|
|
|
|
def test_compare_success(self, client, db_session):
|
|
winner = EloRating(global_id="w1", elo_rating=1500.0, comparison_count=0, wins=0, losses=0)
|
|
loser = EloRating(global_id="l1", elo_rating=1500.0, comparison_count=0, wins=0, losses=0)
|
|
|
|
def fake_filter_by(global_id):
|
|
mock_result = MagicMock()
|
|
if global_id == "w1":
|
|
mock_result.first.return_value = winner
|
|
elif global_id == "l1":
|
|
mock_result.first.return_value = loser
|
|
else:
|
|
mock_result.first.return_value = None
|
|
return mock_result
|
|
|
|
db_session.query.return_value.filter_by = fake_filter_by
|
|
|
|
resp = client.post(
|
|
"/api/compare",
|
|
json={"winner_id": "w1", "loser_id": "l1"},
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["winner_id"] == "w1"
|
|
assert data["loser_id"] == "l1"
|
|
assert data["elo_change"] > 0
|
|
|
|
def test_matchup_insufficient_listings(self, client, db_session):
|
|
db_session.execute.return_value = [_fake_listing()] # only 1
|
|
resp = client.get("/api/matchup")
|
|
assert resp.status_code == 400
|
|
|
|
def test_history(self, client, db_session):
|
|
row = MagicMock()
|
|
row.id = 1
|
|
row.listing_a_title = "House A"
|
|
row.listing_b_title = "House B"
|
|
row.winner_title = "House A"
|
|
row.listing_a_id = "a"
|
|
row.listing_b_id = "b"
|
|
row.winner_id = "a"
|
|
row.elo_a_before = 1500.0
|
|
row.elo_b_before = 1500.0
|
|
row.elo_a_after = 1516.0
|
|
row.elo_b_after = 1484.0
|
|
row.created_at = datetime(2024, 1, 1, 12, 0, 0)
|
|
db_session.execute.return_value = [row]
|
|
resp = client.get("/api/history")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 1
|
|
assert data[0]["winner_id"] == "a"
|