Add services layer, tests, streaming UI, and cleanup legacy code
This commit is contained in:
parent
5514fa6381
commit
d205d15c74
62 changed files with 3729 additions and 1024 deletions
180
crawler/tests/integration/test_api.py
Normal file
180
crawler/tests/integration/test_api.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
"""Integration tests for API endpoints."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
from api.auth import User
|
||||
|
||||
|
||||
class TestStatusEndpoint:
|
||||
"""Tests for the /api/status endpoint."""
|
||||
|
||||
async def test_status_endpoint_returns_ok(
|
||||
self, async_client: AsyncClient
|
||||
) -> None:
|
||||
"""Test that status endpoint returns OK status."""
|
||||
response = await async_client.get("/api/status")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"status": "OK"}
|
||||
|
||||
|
||||
class TestListingEndpoint:
|
||||
"""Tests for the /api/listing endpoint."""
|
||||
|
||||
async def test_listing_endpoint_requires_auth(self) -> None:
|
||||
"""Test that listing endpoint requires authentication."""
|
||||
from api.app import app
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
# Clear any dependency overrides to test auth requirement
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.get("/api/listing")
|
||||
# Should return 401 or 403 without valid auth
|
||||
assert response.status_code in (401, 403)
|
||||
|
||||
async def test_listing_endpoint_with_auth(
|
||||
self, async_client: AsyncClient
|
||||
) -> None:
|
||||
"""Test that listing endpoint works with authentication."""
|
||||
# Mock the repository to return empty list
|
||||
with patch(
|
||||
"api.app.ListingRepository.get_listings",
|
||||
new_callable=AsyncMock,
|
||||
return_value=[],
|
||||
):
|
||||
response = await async_client.get("/api/listing")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "listings" in data
|
||||
|
||||
|
||||
class TestListingGeoJsonEndpoint:
|
||||
"""Tests for the /api/listing_geojson endpoint."""
|
||||
|
||||
async def test_listing_geojson_requires_auth(self) -> None:
|
||||
"""Test that listing_geojson endpoint requires authentication."""
|
||||
from api.app import app
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
# Clear any dependency overrides to test auth requirement
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.get(
|
||||
"/api/listing_geojson",
|
||||
params={"listing_type": "RENT"},
|
||||
)
|
||||
# Should return 401 or 403 without valid auth
|
||||
assert response.status_code in (401, 403)
|
||||
|
||||
async def test_listing_geojson_with_filters(
|
||||
self, async_client: AsyncClient
|
||||
) -> None:
|
||||
"""Test that listing_geojson accepts filter parameters."""
|
||||
with patch(
|
||||
"api.app.export_immoweb",
|
||||
new_callable=AsyncMock,
|
||||
return_value={"type": "FeatureCollection", "features": []},
|
||||
):
|
||||
response = await async_client.get(
|
||||
"/api/listing_geojson",
|
||||
params={
|
||||
"listing_type": "RENT",
|
||||
"min_bedrooms": 2,
|
||||
"max_bedrooms": 3,
|
||||
"min_price": 1500,
|
||||
"max_price": 3000,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["type"] == "FeatureCollection"
|
||||
|
||||
|
||||
class TestGetDistrictsEndpoint:
|
||||
"""Tests for the /api/get_districts endpoint."""
|
||||
|
||||
async def test_get_districts_requires_auth(self) -> None:
|
||||
"""Test that get_districts endpoint requires authentication."""
|
||||
from api.app import app
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
# Clear any dependency overrides to test auth requirement
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.get("/api/get_districts")
|
||||
# Should return 401 or 403 without valid auth
|
||||
assert response.status_code in (401, 403)
|
||||
|
||||
async def test_get_districts_returns_dict(
|
||||
self, async_client: AsyncClient
|
||||
) -> None:
|
||||
"""Test that get_districts returns a dictionary of districts."""
|
||||
response = await async_client.get("/api/get_districts")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, dict)
|
||||
# Check some known districts exist
|
||||
assert "London" in data
|
||||
assert "Westminster" in data
|
||||
assert "Camden" in data
|
||||
|
||||
async def test_get_districts_values_are_region_ids(
|
||||
self, async_client: AsyncClient
|
||||
) -> None:
|
||||
"""Test that district values are REGION identifiers."""
|
||||
response = await async_client.get("/api/get_districts")
|
||||
data = response.json()
|
||||
# All values should be REGION^... format
|
||||
for district_name, region_id in data.items():
|
||||
assert region_id.startswith("REGION^"), (
|
||||
f"District {district_name} has invalid region ID: {region_id}"
|
||||
)
|
||||
|
||||
|
||||
class TestRefreshListingsEndpoint:
|
||||
"""Tests for the /api/refresh_listings endpoint."""
|
||||
|
||||
async def test_refresh_listings_requires_auth(self) -> None:
|
||||
"""Test that refresh_listings endpoint requires authentication."""
|
||||
from api.app import app
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
# Clear any dependency overrides to test auth requirement
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.post(
|
||||
"/api/refresh_listings",
|
||||
params={"listing_type": "RENT"},
|
||||
)
|
||||
# Should return 401 or 403 without valid auth
|
||||
assert response.status_code in (401, 403)
|
||||
|
||||
|
||||
class TestTaskStatusEndpoint:
|
||||
"""Tests for the /api/task_status endpoint."""
|
||||
|
||||
async def test_task_status_requires_auth(self) -> None:
|
||||
"""Test that task_status endpoint requires authentication."""
|
||||
from api.app import app
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
# Clear any dependency overrides to test auth requirement
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
response = await client.get(
|
||||
"/api/task_status",
|
||||
params={"task_id": "test-task-id"},
|
||||
)
|
||||
# Should return 401 or 403 without valid auth
|
||||
assert response.status_code in (401, 403)
|
||||
Loading…
Add table
Add a link
Reference in a new issue