mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-10 19:03:03 +02:00
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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user