from datetime import datetime, timedelta, timezone import pytest from httpx import ASGITransport, AsyncClient from app.api.v1.visualization import convert_vessels_to_geojson from app.db.session import get_db from app.main import app from app.models.vessel import VesselPosition, VesselStatic from app.services import barentswatch from app.services.collectors.vessel_ais import VesselAISCollector def test_vessel_collector_transforms_barentswatch_like_records(): collector = VesselAISCollector() records = collector.transform( [ { "mmsi": "257123000", "lat": "59.91", "lon": "10.73", "sog": 12.4, "cog": 214, "nav_status": 0, "shipType": 70, "name": "OSLO TRADER", }, {"mmsi": "bad", "lat": 120, "lon": 10}, ] ) assert len(records) == 1 assert records[0]["mmsi"] == 257123000 assert records[0]["vessel_type_name"] == "Cargo" assert records[0]["lat"] == pytest.approx(59.91) def test_barentswatch_reads_credentials_from_zshrc(tmp_path): zshrc = tmp_path / ".zshrc" zshrc.write_text( "\n".join( [ "export BARENTSWATCH_CLIENT_ID='client-from-zshrc'", 'export BARENTSWATCH_CLIENT_SECRET="secret-from-zshrc" # local dev credential', ] ), encoding="utf-8", ) values = barentswatch._read_zshrc_env(zshrc) assert values["BARENTSWATCH_CLIENT_ID"] == "client-from-zshrc" assert values["BARENTSWATCH_CLIENT_SECRET"] == "secret-from-zshrc" @pytest.mark.asyncio async def test_barentswatch_resolves_config_from_zshrc(tmp_path, monkeypatch): zshrc = tmp_path / ".zshrc" zshrc.write_text( "\n".join( [ "export BARENTSWATCH_CLIENT_ID=client-from-zshrc", "export BARENTSWATCH_CLIENT_SECRET=secret-from-zshrc", ] ), encoding="utf-8", ) monkeypatch.delenv("BARENTSWATCH_CLIENT_ID", raising=False) monkeypatch.delenv("BARENTSWATCH_CLIENT_SECRET", raising=False) monkeypatch.delenv("BARRENTSWATCH_CLIENT_ID", raising=False) monkeypatch.delenv("BARRENTSWATCH_CLIENT_SECRET", raising=False) monkeypatch.setattr(barentswatch.Path, "home", lambda: tmp_path) config = await barentswatch.resolve_barentswatch_config(None) assert config.client_id == "client-from-zshrc" assert config.client_secret == "secret-from-zshrc" assert config.credential_source == "~/.zshrc" def test_convert_vessels_to_geojson(): position = VesselPosition( mmsi=257123000, lat=59.91, lon=10.73, sog=12.4, cog=214, heading=215, nav_status=0, received_at=datetime(2026, 4, 28, 1, 0, tzinfo=timezone.utc), ) static = VesselStatic( mmsi=257123000, name="OSLO TRADER", vessel_type=70, vessel_type_name="Cargo", flag="NO", length=185, ) payload = convert_vessels_to_geojson([(position, static)]) assert payload["type"] == "FeatureCollection" assert payload["features"][0]["geometry"]["coordinates"] == [10.73, 59.91] assert payload["features"][0]["properties"]["mmsi"] == 257123000 assert payload["features"][0]["properties"]["vessel_type_name"] == "Cargo" @pytest.mark.asyncio async def test_vessels_geojson_endpoint_filters_type_and_bbox(): now = datetime(2026, 4, 28, 1, 0, tzinfo=timezone.utc) rows = [ ( VesselPosition(mmsi=1, lat=59.9, lon=10.7, received_at=now), VesselStatic(mmsi=1, name="Cargo Ship", vessel_type=70, vessel_type_name="Cargo"), ), ( VesselPosition(mmsi=2, lat=60.3, lon=5.3, received_at=now - timedelta(minutes=1)), VesselStatic(mmsi=2, name="Passenger Ship", vessel_type=60, vessel_type_name="Passenger"), ), ] class _Result: def all(self): return rows class _FakeSession: async def execute(self, _query): return _Result() async def override_get_db(): yield _FakeSession() app.dependency_overrides[get_db] = override_get_db transport = ASGITransport(app=app) try: async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get( "/api/v1/visualization/geo/vessels", params={"bbox": "0,50,20,70", "type": "cargo"}, ) assert response.status_code == 200 data = response.json() assert data["count"] == 1 assert data["features"][0]["properties"]["name"] == "Cargo Ship" assert data["stats"]["by_type"]["Cargo"] == 1 finally: app.dependency_overrides.clear()