Add unified search API endpoint /api/v1/search/{hash}

- Create src/api/v1/search.py with search() handler
- Add /search/{hash:str} route to v1 router
- Add tests/test_search_api.py for integration tests

Note: Tests fail until Task 3 initializes search_repository in app state.
This commit is contained in:
waclaw-claw 2026-04-09 12:40:41 -04:00
parent 33f3356f2b
commit 52f0f8d1bd
3 changed files with 76 additions and 3 deletions

View File

@ -1,6 +1,6 @@
from fastapi import APIRouter
from . import blocks, fork_choice, health, index, transactions
from . import blocks, fork_choice, health, index, search, transactions
def create_v1_router() -> APIRouter:
@ -14,10 +14,16 @@ def create_v1_router() -> APIRouter:
router.add_api_route("/health/stream", health.stream, methods=["GET", "HEAD"])
router.add_api_route("/health", health.get, methods=["GET", "HEAD"])
router.add_api_route("/search/{hash:str}", search.search, methods=["GET"])
router.add_api_route("/transactions/stream", transactions.stream, methods=["GET"])
router.add_api_route("/transactions/list", transactions.list_transactions, methods=["GET"])
router.add_api_route(
"/transactions/list", transactions.list_transactions, methods=["GET"]
)
router.add_api_route("/transactions/search", transactions.search, methods=["GET"])
router.add_api_route("/transactions/{transaction_hash:str}", transactions.get, methods=["GET"])
router.add_api_route(
"/transactions/{transaction_hash:str}", transactions.get, methods=["GET"]
)
router.add_api_route("/fork-choice", fork_choice.get, methods=["GET"])

39
src/api/v1/search.py Normal file
View File

@ -0,0 +1,39 @@
from http.client import BAD_REQUEST, NOT_FOUND
from typing import TYPE_CHECKING
from fastapi import Path
from starlette.responses import JSONResponse, Response
from core.api import NBERequest
from core.types import dehexify
if TYPE_CHECKING:
from core.app import NBE
async def search(request: NBERequest, hash: str = Path(...)) -> Response:
"""
Search for a block or transaction by hash.
Returns:
- 200 with {"type": "block"|"transaction", "id": int} if found
- 404 if not found
- 400 if hash is invalid
"""
if not hash:
return Response(status_code=BAD_REQUEST)
try:
if hash.startswith("0x"):
hash = hash[2:]
normalized_hash = dehexify(hash)
except ValueError:
return Response(status_code=BAD_REQUEST)
result = await request.app.state.search_repository.search_by_hash(normalized_hash)
if result is None:
return Response(status_code=NOT_FOUND)
result_type, result_id = result
return JSONResponse({"type": result_type, "id": result_id})

28
tests/test_search_api.py Normal file
View File

@ -0,0 +1,28 @@
"""Tests for Search API endpoint."""
import pytest
from fastapi.testclient import TestClient
from src.app import create_app
app = create_app("")
client = TestClient(app)
@pytest.mark.asyncio
async def test_search_block_hash():
response = client.get("/api/v1/search/0x" + "00" * 32)
# Should return 404 for non-existent block
assert response.status_code == 404
@pytest.mark.asyncio
async def test_search_transaction_hash():
response = client.get("/api/v1/search/0x" + "11" * 32)
# Should return 404 for non-existent transaction
assert response.status_code == 404
@pytest.mark.asyncio
async def test_search_invalid_hash():
response = client.get("/api/v1/search/invalid")
assert response.status_code == 400