mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-03-28 17:42:56 +01:00
Merge pull request #70 from ipnet-mesh/chore/node-page-prefix-support
Add prefix matching support to node API endpoint
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi import APIRouter, HTTPException, Path, Query
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
@@ -81,10 +81,24 @@ async def list_nodes(
|
||||
async def get_node(
|
||||
_: RequireRead,
|
||||
session: DbSession,
|
||||
public_key: str,
|
||||
public_key: str = Path(
|
||||
description="Full public key or prefix. If multiple nodes match the prefix, "
|
||||
"the first one (alphabetically) is returned."
|
||||
),
|
||||
) -> NodeRead:
|
||||
"""Get a single node by public key."""
|
||||
query = select(Node).where(Node.public_key == public_key)
|
||||
"""Get a single node by public key or prefix.
|
||||
|
||||
Supports prefix matching - you can provide any number of leading characters
|
||||
of a public key. If multiple nodes match the prefix, the first one
|
||||
(alphabetically by public_key) is returned.
|
||||
"""
|
||||
query = (
|
||||
select(Node)
|
||||
.options(selectinload(Node.tags))
|
||||
.where(Node.public_key.startswith(public_key))
|
||||
.order_by(Node.public_key)
|
||||
.limit(1)
|
||||
)
|
||||
node = session.execute(query).scalar_one_or_none()
|
||||
|
||||
if not node:
|
||||
|
||||
@@ -146,6 +146,54 @@ class TestGetNode:
|
||||
response = client_no_auth.get("/api/v1/nodes/nonexistent123")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_get_node_by_prefix(self, client_no_auth, sample_node):
|
||||
"""Test getting a node by public key prefix."""
|
||||
prefix = sample_node.public_key[:8] # First 8 chars
|
||||
response = client_no_auth.get(f"/api/v1/nodes/{prefix}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["public_key"] == sample_node.public_key
|
||||
|
||||
def test_get_node_by_single_char_prefix(self, client_no_auth, sample_node):
|
||||
"""Test getting a node by single character prefix."""
|
||||
prefix = sample_node.public_key[0]
|
||||
response = client_no_auth.get(f"/api/v1/nodes/{prefix}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["public_key"] == sample_node.public_key
|
||||
|
||||
def test_get_node_prefix_returns_first_alphabetically(
|
||||
self, client_no_auth, api_db_session
|
||||
):
|
||||
"""Test that prefix match returns first node alphabetically."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from meshcore_hub.common.models import Node
|
||||
|
||||
# Create two nodes with same prefix but different suffixes
|
||||
# abc... should come before abd...
|
||||
node_a = Node(
|
||||
public_key="abc0000000000000000000000000000000000000000000000000000000000000",
|
||||
name="Node A",
|
||||
adv_type="REPEATER",
|
||||
first_seen=datetime.now(timezone.utc),
|
||||
)
|
||||
node_b = Node(
|
||||
public_key="abc1111111111111111111111111111111111111111111111111111111111111",
|
||||
name="Node B",
|
||||
adv_type="REPEATER",
|
||||
first_seen=datetime.now(timezone.utc),
|
||||
)
|
||||
api_db_session.add(node_a)
|
||||
api_db_session.add(node_b)
|
||||
api_db_session.commit()
|
||||
|
||||
# Request with prefix should return first alphabetically
|
||||
response = client_no_auth.get("/api/v1/nodes/abc")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["public_key"] == node_a.public_key
|
||||
|
||||
|
||||
class TestNodeTags:
|
||||
"""Tests for node tag endpoints."""
|
||||
|
||||
Reference in New Issue
Block a user