Add bind-time initial location fallback
This commit is contained in:
@@ -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 == []
|
||||
|
||||
Reference in New Issue
Block a user