feat: add route type tracking and flood-only defaults for advertisements

Track advertisement route type (flood/transport_flood/direct/transport_direct)
and node advert timestamp to distinguish zero-hop from flood adverts, improve
deduplication with 300s buckets, and default all dashboard/ad-API queries to
flood-only (including NULL for historical records).
This commit is contained in:
Louis King
2026-05-15 20:55:48 +01:00
parent 2d5bea2460
commit 9afff5bc70
21 changed files with 1150 additions and 26 deletions
+137
View File
@@ -525,3 +525,140 @@ class TestAdvertisementSort:
assert response.status_code == 200
items = response.json()["items"]
assert items[0]["name"] == "New"
class TestListAdvertisementsRouteTypeFilter:
"""Tests for route_type query parameter on advertisements endpoint."""
def test_default_filter_shows_flood_and_null(self, client_no_auth, api_db_session):
"""Default route_type filter shows flood, transport_flood, and NULL."""
now = datetime.now(timezone.utc)
flood_ad = Advertisement(
public_key="aa" * 16,
name="Flood",
adv_type="CLIENT",
received_at=now,
route_type="flood",
)
null_ad = Advertisement(
public_key="bb" * 16,
name="Historical",
adv_type="CLIENT",
received_at=now,
route_type=None,
)
direct_ad = Advertisement(
public_key="cc" * 16,
name="Direct",
adv_type="CLIENT",
received_at=now,
route_type="direct",
)
api_db_session.add_all([flood_ad, null_ad, direct_ad])
api_db_session.commit()
response = client_no_auth.get("/api/v1/advertisements")
assert response.status_code == 200
data = response.json()
assert data["total"] == 2
names = {item["name"] for item in data["items"]}
assert names == {"Flood", "Historical"}
def test_filter_all_shows_all(self, client_no_auth, api_db_session):
"""route_type=all shows all advertisements."""
now = datetime.now(timezone.utc)
flood_ad = Advertisement(
public_key="aa" * 16,
name="Flood",
adv_type="CLIENT",
received_at=now,
route_type="flood",
)
direct_ad = Advertisement(
public_key="cc" * 16,
name="Direct",
adv_type="CLIENT",
received_at=now,
route_type="direct",
)
api_db_session.add_all([flood_ad, direct_ad])
api_db_session.commit()
response = client_no_auth.get("/api/v1/advertisements?route_type=all")
assert response.status_code == 200
data = response.json()
assert data["total"] == 2
def test_filter_direct_only(self, client_no_auth, api_db_session):
"""route_type=direct shows only direct and NULL."""
now = datetime.now(timezone.utc)
flood_ad = Advertisement(
public_key="aa" * 16,
name="Flood",
adv_type="CLIENT",
received_at=now,
route_type="flood",
)
direct_ad = Advertisement(
public_key="cc" * 16,
name="Direct",
adv_type="CLIENT",
received_at=now,
route_type="direct",
)
null_ad = Advertisement(
public_key="dd" * 16,
name="Historical",
adv_type="CLIENT",
received_at=now,
route_type=None,
)
api_db_session.add_all([flood_ad, direct_ad, null_ad])
api_db_session.commit()
response = client_no_auth.get("/api/v1/advertisements?route_type=direct")
assert response.status_code == 200
data = response.json()
assert data["total"] == 2
names = {item["name"] for item in data["items"]}
assert names == {"Direct", "Historical"}
def test_route_type_in_response(self, client_no_auth, api_db_session):
"""route_type and advert_timestamp are included in response."""
now = datetime.now(timezone.utc)
ad = Advertisement(
public_key="aa" * 16,
name="Test",
adv_type="CLIENT",
received_at=now,
route_type="flood",
)
api_db_session.add(ad)
api_db_session.commit()
response = client_no_auth.get("/api/v1/advertisements")
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 1
assert data["items"][0]["route_type"] == "flood"
assert data["items"][0]["advert_timestamp"] is None
def test_get_advertisement_includes_route_type(
self, client_no_auth, api_db_session
):
"""GET /{id} includes route_type and advert_timestamp."""
now = datetime.now(timezone.utc)
ad = Advertisement(
public_key="aa" * 16,
name="Test",
adv_type="CLIENT",
received_at=now,
route_type="transport_flood",
)
api_db_session.add(ad)
api_db_session.commit()
response = client_no_auth.get(f"/api/v1/advertisements/{ad.id}")
assert response.status_code == 200
data = response.json()
assert data["route_type"] == "transport_flood"
+88
View File
@@ -351,3 +351,91 @@ class TestDashboardTestUserExclusion:
data = response.json()
assert data["total_operators"] == 0
assert data["total_members"] == 0
class TestDashboardFloodOnlyFilter:
"""Tests for flood-only advertisement filtering on dashboard."""
def test_stats_excludes_direct_adverts(self, client_no_auth, api_db_session):
"""Dashboard stats exclude direct (zero-hop) advertisements."""
now = datetime.now(timezone.utc)
flood_ad = Advertisement(
public_key="aa" * 16,
name="Flood",
adv_type="CLIENT",
received_at=now,
route_type="flood",
)
direct_ad = Advertisement(
public_key="bb" * 16,
name="Direct",
adv_type="CLIENT",
received_at=now,
route_type="direct",
)
null_ad = Advertisement(
public_key="cc" * 16,
name="Historical",
adv_type="CLIENT",
received_at=now,
route_type=None,
)
api_db_session.add_all([flood_ad, direct_ad, null_ad])
api_db_session.commit()
response = client_no_auth.get("/api/v1/dashboard/stats")
assert response.status_code == 200
data = response.json()
assert data["total_advertisements"] == 2
def test_recent_ads_excludes_direct(self, client_no_auth, api_db_session):
"""Recent advertisements list excludes direct adverts."""
now = datetime.now(timezone.utc)
direct_ad = Advertisement(
public_key="aa" * 16,
name="Direct",
adv_type="CLIENT",
received_at=now,
route_type="direct",
)
flood_ad = Advertisement(
public_key="bb" * 16,
name="Flood",
adv_type="CLIENT",
received_at=now - timedelta(seconds=1),
route_type="flood",
)
api_db_session.add_all([direct_ad, flood_ad])
api_db_session.commit()
response = client_no_auth.get("/api/v1/dashboard/stats")
assert response.status_code == 200
data = response.json()
assert len(data["recent_advertisements"]) == 1
assert data["recent_advertisements"][0]["name"] == "Flood"
def test_activity_excludes_direct(self, client_no_auth, api_db_session):
"""Activity endpoint excludes direct advertisements."""
yesterday = datetime.now(timezone.utc) - timedelta(days=1)
direct_ad = Advertisement(
public_key="aa" * 16,
name="Direct",
adv_type="CLIENT",
received_at=yesterday,
route_type="direct",
)
flood_ad = Advertisement(
public_key="bb" * 16,
name="Flood",
adv_type="CLIENT",
received_at=yesterday,
route_type="flood",
)
api_db_session.add_all([direct_ad, flood_ad])
api_db_session.commit()
response = client_no_auth.get("/api/v1/dashboard/activity")
assert response.status_code == 200
data = response.json()
total_count = sum(point["count"] for point in data["data"])
assert total_count == 1