mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-23 05:06:38 +00:00
feat: stage3 — REST API endpoints and dummy FlightProcessor
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""Integration tests for the Flight API endpoints."""
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
|
||||
from gps_denied.app import app
|
||||
from gps_denied.db.engine import get_session
|
||||
from gps_denied.db.models import Base
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def override_get_session():
|
||||
"""Create an in-memory SQLite db for API tests."""
|
||||
engine = create_async_engine("sqlite+aiosqlite://", echo=False)
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async def _get_session():
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
|
||||
app.dependency_overrides[get_session] = _get_session
|
||||
yield
|
||||
app.dependency_overrides.clear()
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(override_get_session) -> AsyncClient:
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app), base_url="http://test"
|
||||
) as ac:
|
||||
yield ac
|
||||
|
||||
|
||||
# ── Payload Fixtures ──────────────────────────────────────────────────────
|
||||
|
||||
FLIGHT_PAYLOAD = {
|
||||
"name": "Integration_Test_Flight",
|
||||
"description": "API Test",
|
||||
"start_gps": {"lat": 48.1, "lon": 37.2},
|
||||
"rough_waypoints": [{"lat": 48.11, "lon": 37.21}],
|
||||
"geofences": {"polygons": []},
|
||||
"camera_params": {
|
||||
"focal_length": 25.0,
|
||||
"sensor_width": 23.5,
|
||||
"sensor_height": 15.6,
|
||||
"resolution_width": 6252,
|
||||
"resolution_height": 4168
|
||||
},
|
||||
"altitude": 500.0
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_flight(client: AsyncClient):
|
||||
resp = await client.post("/flights", json=FLIGHT_PAYLOAD)
|
||||
assert resp.status_code == 201
|
||||
data = resp.json()
|
||||
assert "flight_id" in data
|
||||
assert data["status"] == "prefetching"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_flight_details(client: AsyncClient):
|
||||
# 1. Create flight
|
||||
resp1 = await client.post("/flights", json=FLIGHT_PAYLOAD)
|
||||
fid = resp1.json()["flight_id"]
|
||||
|
||||
# 2. Get flight
|
||||
resp2 = await client.get(f"/flights/{fid}")
|
||||
assert resp2.status_code == 200
|
||||
data = resp2.json()
|
||||
assert data["flight_id"] == fid
|
||||
assert data["name"] == "Integration_Test_Flight"
|
||||
assert len(data["waypoints"]) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_image_batch(client: AsyncClient):
|
||||
# 1. Create flight
|
||||
resp1 = await client.post("/flights", json=FLIGHT_PAYLOAD)
|
||||
fid = resp1.json()["flight_id"]
|
||||
|
||||
# 2. Upload Batch
|
||||
meta = {
|
||||
"start_sequence": 1,
|
||||
"end_sequence": 10,
|
||||
"batch_number": 1
|
||||
}
|
||||
files = [("images", ("test1.jpg", b"dummy", "image/jpeg")) for _ in range(10)]
|
||||
|
||||
resp2 = await client.post(
|
||||
f"/flights/{fid}/images/batch",
|
||||
data={"metadata": json.dumps(meta)},
|
||||
files=files
|
||||
)
|
||||
assert resp2.status_code == 202
|
||||
data = resp2.json()
|
||||
assert data["accepted"] is True
|
||||
assert data["next_expected"] == 11
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_fix(client: AsyncClient):
|
||||
# 1. Create flight
|
||||
resp1 = await client.post("/flights", json=FLIGHT_PAYLOAD)
|
||||
fid = resp1.json()["flight_id"]
|
||||
|
||||
# 2. Submit fix
|
||||
fix_data = {
|
||||
"frame_id": 5,
|
||||
"uav_pixel": [1024.0, 768.0],
|
||||
"satellite_gps": {"lat": 48.11, "lon": 37.22}
|
||||
}
|
||||
resp2 = await client.post(f"/flights/{fid}/user-fix", json=fix_data)
|
||||
assert resp2.status_code == 200
|
||||
data = resp2.json()
|
||||
assert data["processing_resumed"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_flight_status(client: AsyncClient):
|
||||
# 1. Create
|
||||
resp1 = await client.post("/flights", json=FLIGHT_PAYLOAD)
|
||||
fid = resp1.json()["flight_id"]
|
||||
|
||||
# 2. Status
|
||||
resp2 = await client.get(f"/flights/{fid}/status")
|
||||
assert resp2.status_code == 200
|
||||
assert resp2.json()["status"] == "created" # The initial state from DB
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sse_stream(client: AsyncClient):
|
||||
resp1 = await client.post("/flights", json=FLIGHT_PAYLOAD)
|
||||
fid = resp1.json()["flight_id"]
|
||||
|
||||
async with client.stream("GET", f"/flights/{fid}/stream") as resp:
|
||||
assert resp.status_code == 200
|
||||
# Just grab the first chunk to verify connection
|
||||
chunk = await anext(resp.aiter_bytes())
|
||||
assert chunk is not None
|
||||
Reference in New Issue
Block a user