mirror of
https://github.com/logos-blockchain/logos-blockchain-block-explorer-template.git
synced 2026-05-18 07:19:27 +00:00
feat: add SearchRepository for hash-based search
This commit is contained in:
parent
c49574b6cb
commit
33f3356f2b
52
src/db/search.py
Normal file
52
src/db/search.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""Repository for search operations across blocks and transactions."""
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from sqlmodel import select
|
||||
|
||||
from db.clients import DbClient
|
||||
from models.block import Block
|
||||
from models.transactions.transaction import Transaction
|
||||
|
||||
|
||||
class SearchRepository:
|
||||
"""Repository for search operations across blocks and transactions."""
|
||||
|
||||
def __init__(self, client: DbClient):
|
||||
self.client = client
|
||||
|
||||
async def search_by_hash(self, hash_value: str) -> Optional[Tuple[str, int]]:
|
||||
"""
|
||||
Search for a block or transaction by hash.
|
||||
|
||||
Args:
|
||||
hash_value: Hex string hash (with or without 0x prefix)
|
||||
|
||||
Returns:
|
||||
Tuple of (type, id) where type is "block" or "transaction",
|
||||
or None if not found.
|
||||
"""
|
||||
# Normalize hash (handle 0x prefix)
|
||||
if hash_value.startswith("0x"):
|
||||
hash_bytes = bytes.fromhex(hash_value[2:])
|
||||
else:
|
||||
hash_bytes = bytes.fromhex(hash_value)
|
||||
|
||||
with self.client.session() as session:
|
||||
# Try to find as block first
|
||||
block_result = session.exec(
|
||||
select(Block).where(Block.hash == hash_bytes)
|
||||
).first()
|
||||
|
||||
if block_result:
|
||||
return ("block", block_result.id)
|
||||
|
||||
# Try to find as transaction
|
||||
tx_result = session.exec(
|
||||
select(Transaction).where(Transaction.hash == hash_bytes)
|
||||
).first()
|
||||
|
||||
if tx_result:
|
||||
return ("transaction", tx_result.id)
|
||||
|
||||
return None
|
||||
19
tests/conftest.py
Normal file
19
tests/conftest.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""Pytest configuration and fixtures."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
# Add src directory to path for imports
|
||||
src_path = Path(__file__).parent.parent / "src"
|
||||
if str(src_path) not in sys.path:
|
||||
sys.path.insert(0, str(src_path))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop_policy():
|
||||
"""Use default event loop policy."""
|
||||
import asyncio
|
||||
|
||||
return asyncio.DefaultEventLoopPolicy()
|
||||
128
tests/test_search.py
Normal file
128
tests/test_search.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""Tests for SearchRepository."""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from db.clients.sqlite import SqliteClient
|
||||
from db.search import SearchRepository
|
||||
from models.block import Block
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_by_block_hash():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = f"sqlite:///{Path(tmpdir) / 'test.db'}"
|
||||
client = SqliteClient(db_path)
|
||||
repo = SearchRepository(client)
|
||||
|
||||
# Search for non-existent block should return None
|
||||
result = await repo.search_by_hash("0x" + "00" * 32)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_by_transaction_hash():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = f"sqlite:///{Path(tmpdir) / 'test.db'}"
|
||||
client = SqliteClient(db_path)
|
||||
repo = SearchRepository(client)
|
||||
|
||||
# Search for non-existent transaction should return None
|
||||
result = await repo.search_by_hash("0x" + "11" * 32)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_finds_block():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = f"sqlite:///{Path(tmpdir) / 'test.db'}"
|
||||
client = SqliteClient(db_path)
|
||||
repo = SearchRepository(client)
|
||||
|
||||
# Create a block
|
||||
test_hash = bytes.fromhex("aa" * 32)
|
||||
block = Block(
|
||||
hash=test_hash,
|
||||
parent_block=b"\x00" * 32,
|
||||
slot=1,
|
||||
height=0,
|
||||
block_root=b"\x00" * 32,
|
||||
proof_of_leadership=None,
|
||||
)
|
||||
|
||||
with client.session() as session:
|
||||
session.add(block)
|
||||
session.flush()
|
||||
block_id = block.id
|
||||
session.commit()
|
||||
|
||||
# Search for the block
|
||||
result = await repo.search_by_hash("0x" + "aa" * 32)
|
||||
assert result is not None
|
||||
assert result[0] == "block"
|
||||
assert result[1] == block_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_without_0x_prefix():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = f"sqlite:///{Path(tmpdir) / 'test.db'}"
|
||||
client = SqliteClient(db_path)
|
||||
repo = SearchRepository(client)
|
||||
|
||||
# Create a block
|
||||
test_hash = bytes.fromhex("dd" * 32)
|
||||
block = Block(
|
||||
hash=test_hash,
|
||||
parent_block=b"\x00" * 32,
|
||||
slot=1,
|
||||
height=0,
|
||||
block_root=b"\x00" * 32,
|
||||
proof_of_leadership=None,
|
||||
)
|
||||
|
||||
with client.session() as session:
|
||||
session.add(block)
|
||||
session.flush()
|
||||
session.commit()
|
||||
|
||||
# Search without 0x prefix
|
||||
result = await repo.search_by_hash("dd" * 32)
|
||||
assert result is not None
|
||||
assert result[0] == "block"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_returns_tuple_with_id():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = f"sqlite:///{Path(tmpdir) / 'test.db'}"
|
||||
client = SqliteClient(db_path)
|
||||
repo = SearchRepository(client)
|
||||
|
||||
# Create a block
|
||||
test_hash = bytes.fromhex("ee" * 32)
|
||||
block = Block(
|
||||
hash=test_hash,
|
||||
parent_block=b"\x00" * 32,
|
||||
slot=1,
|
||||
height=0,
|
||||
block_root=b"\x00" * 32,
|
||||
proof_of_leadership=None,
|
||||
)
|
||||
|
||||
with client.session() as session:
|
||||
session.add(block)
|
||||
session.flush()
|
||||
block_id = block.id
|
||||
session.commit()
|
||||
|
||||
# Search should return tuple (type, id)
|
||||
result = await repo.search_by_hash("0x" + "ee" * 32)
|
||||
assert result is not None
|
||||
assert isinstance(result, tuple)
|
||||
assert len(result) == 2
|
||||
assert result[0] == "block"
|
||||
assert isinstance(result[1], int)
|
||||
assert result[1] > 0
|
||||
Loading…
x
Reference in New Issue
Block a user