Add bind-time initial location fallback

This commit is contained in:
stu2not
2026-06-30 08:05:01 +08:00
parent 0283aa2fec
commit f33205d1bd
6 changed files with 330 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
import pytest
from banban.schemas.location import DeviceLocationReportRequest
from banban.dao.location import LocationDAO
from banban.service.location import (
LocationService,
normalize_location_coordinate,
@@ -115,3 +116,167 @@ async def test_http_location_report_without_coord_type_converts_before_persist(m
assert persisted_payload.coord_type == "gcj02"
assert persisted_payload.lat == pytest.approx(30.22066658053894)
assert persisted_payload.lng == pytest.approx(120.26284572518867)
@pytest.mark.asyncio
async def test_bind_initial_location_writes_only_when_no_valid_location(monkeypatch):
service = LocationService()
captured = {}
class FakeAccess:
child_id = 9
class FakeSession:
async def close(self):
captured["closed"] = True
class FakeLocationDAO:
def __init__(self, session):
captured["session"] = session
async def assert_parent_device_access(self, *, device_id, user_id):
captured["access"] = (device_id, user_id)
return FakeAccess()
async def has_valid_location(self, *, device_id, child_id):
captured["valid_check"] = (device_id, child_id)
return False
async def report_device_location(self, *, device_id, child_id, payload):
captured["report"] = (device_id, child_id, payload)
return {
"child_id": child_id,
"device_id": device_id,
"coord_type": payload.coord_type,
"lat": payload.lat,
"lng": payload.lng,
"should_resolve_address": False,
}
async def fake_get_session():
return FakeSession()
monkeypatch.setattr("banban.service.location.LocationDAO", FakeLocationDAO)
monkeypatch.setattr(service, "get_session", fake_get_session)
row = await service.report_bind_initial_location(
device_id="TalkingQ_device001",
user_id=3,
latitude=30.223122166666666,
longitude=120.25837883333334,
coord_type="gcj02",
accuracy_m=20,
)
_, _, payload = captured["report"]
assert captured["access"] == ("TalkingQ_device001", 3)
assert captured["valid_check"] == ("TalkingQ_device001", 9)
assert captured["closed"] is True
assert row["coord_type"] == "gcj02"
assert payload.coord_type == "gcj02"
assert payload.lat == pytest.approx(30.223122166666666)
assert payload.lng == pytest.approx(120.25837883333334)
assert payload.source == 5
assert payload.accuracy_m == 20
@pytest.mark.asyncio
async def test_bind_initial_location_skips_when_valid_location_exists(monkeypatch):
service = LocationService()
captured = {}
class FakeAccess:
child_id = 9
class FakeSession:
async def close(self):
captured["closed"] = True
class FakeLocationDAO:
def __init__(self, session):
del session
async def assert_parent_device_access(self, *, device_id, user_id):
captured["access"] = (device_id, user_id)
return FakeAccess()
async def has_valid_location(self, *, device_id, child_id):
captured["valid_check"] = (device_id, child_id)
return True
async def report_device_location(self, **kwargs):
captured["report"] = kwargs
raise AssertionError("initial location must not overwrite an existing valid location")
async def fake_get_session():
return FakeSession()
monkeypatch.setattr("banban.service.location.LocationDAO", FakeLocationDAO)
monkeypatch.setattr(service, "get_session", fake_get_session)
row = await service.report_bind_initial_location(
device_id="TalkingQ_device001",
user_id=3,
latitude=30.223122166666666,
longitude=120.25837883333334,
coord_type="gcj02",
)
assert row is None
assert captured["access"] == ("TalkingQ_device001", 3)
assert captured["valid_check"] == ("TalkingQ_device001", 9)
assert captured["closed"] is True
assert "report" not in captured
@pytest.mark.asyncio
async def test_has_valid_location_checks_history_when_current_is_empty():
calls = []
class FakeResult:
def __init__(self, row):
self.row = row
def first(self):
return self.row
class FakeDB:
async def execute(self, statement, params=None):
calls.append((str(statement), params))
if len(calls) == 1:
return FakeResult(None)
return FakeResult((1,))
dao = LocationDAO(FakeDB())
exists = await dao.has_valid_location(device_id="TalkingQ_device001", child_id=9)
assert exists is True
assert len(calls) == 2
assert "FROM child_location_current" in calls[0][0]
assert "FROM child_location_history" in calls[1][0]
assert calls[0][1] == {"device_id": "TalkingQ_device001", "child_id": 9}
assert calls[1][1] == {"device_id": "TalkingQ_device001", "child_id": 9}
@pytest.mark.asyncio
async def test_bind_initial_location_ignores_invalid_coordinate_before_db(monkeypatch):
service = LocationService()
sessions = []
async def fail_get_session():
sessions.append("called")
raise AssertionError("invalid bind location should not open a database session")
monkeypatch.setattr(service, "get_session", fail_get_session)
row = await service.report_bind_initial_location(
device_id="TalkingQ_device001",
user_id=3,
latitude=0,
longitude=0,
coord_type="gcj02",
)
assert row is None
assert sessions == []