xsecuremcp / tests /ledger /test_ledger_http.py
svkrishna
🤖 Implement Story 3: Provenance Ledger
7d7a6cb
Raw
History Blame Contribute Delete
11.2 kB
"""Tests for the ledger HTTP endpoints."""
import pytest
import httpx
from fastmcp import FastMCP
from fastmcp.ledger import ProvenanceLedger, LedgerEvent, EventType
@pytest.fixture
def server_with_ledger():
"""Create a FastMCP server with ledger enabled."""
server = FastMCP("TestLedgerServer")
ledger = server.enable_ledger(database_url="sqlite:///:memory:")
return server, ledger
@pytest.fixture
async def client(server_with_ledger):
"""Create an HTTP client for testing."""
server, ledger = server_with_ledger
from fastmcp.server.http import create_streamable_http_app
app = create_streamable_http_app(server, streamable_http_path="/")
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
# Store server and ledger in client for test access
client.server = server
client.ledger = ledger
yield client
class TestLedgerHTTPEndpoints:
"""Test the ledger HTTP endpoints."""
async def test_append_event_endpoint(self, client):
"""Test the append event endpoint."""
event_data = {
"event_type": "tool_call",
"actor_id": "user123",
"resource_id": "resource456",
"action": "execute_tool",
"metadata": {"tool_name": "test_tool"},
"data_hash": "sha256_hash"
}
response = await client.post("/ledger/events", json=event_data)
assert response.status_code == 201
data = response.json()
assert "entry_id" in data
assert "sequence_number" in data
assert "entry_hash" in data
assert "block_id" in data
assert "created_at" in data
assert data["sequence_number"] == 1
async def test_append_event_invalid_data(self, client):
"""Test append event with invalid data."""
invalid_data = {
"event_type": "invalid_type",
"actor_id": "user123",
"action": "test_action"
}
response = await client.post("/ledger/events", json=invalid_data)
assert response.status_code == 400
data = response.json()
assert "error" in data
assert "Invalid event data" in data["error"]
async def test_verify_block_endpoint(self, client):
"""Test the verify block endpoint."""
# First, add some events to create a block
for i in range(3):
event_data = {
"event_type": "tool_call",
"actor_id": f"user{i}",
"action": f"action_{i}"
}
await client.post("/ledger/events", json=event_data)
# Manually seal the block for testing
client.ledger.seal_current_block()
# Verify the block
response = await client.get("/ledger/verify/1")
assert response.status_code == 200
data = response.json()
assert data["block_number"] == 1
assert data["verified"] is True
assert "block_id" in data
assert "entry_count" in data
assert "merkle_root" in data
async def test_verify_nonexistent_block(self, client):
"""Test verifying a non-existent block."""
response = await client.get("/ledger/verify/999")
assert response.status_code == 400
data = response.json()
assert "error" in data
assert "Block integrity verification failed" in data["error"]
async def test_get_entry_endpoint(self, client):
"""Test the get entry endpoint."""
# First, add an event
event_data = {
"event_type": "tool_call",
"actor_id": "user123",
"action": "test_action"
}
await client.post("/ledger/events", json=event_data)
# Get the entry
response = await client.get("/ledger/entries/1")
assert response.status_code == 200
data = response.json()
assert data["sequence_number"] == 1
assert "entry_hash" in data
assert "previous_hash" in data
assert "event" in data
assert data["event"]["actor_id"] == "user123"
assert data["event"]["action"] == "test_action"
async def test_get_nonexistent_entry(self, client):
"""Test getting a non-existent entry."""
response = await client.get("/ledger/entries/999")
assert response.status_code == 404
data = response.json()
assert "error" in data
assert "Entry not found" in data["error"]
async def test_get_block_endpoint(self, client):
"""Test the get block endpoint."""
# First, add some events to create a block
for i in range(3):
event_data = {
"event_type": "tool_call",
"actor_id": f"user{i}",
"action": f"action_{i}"
}
await client.post("/ledger/events", json=event_data)
# Get the block
response = await client.get("/ledger/blocks/1")
assert response.status_code == 200
data = response.json()
assert data["block_number"] == 1
assert "entry_count" in data
assert "merkle_root" in data
assert "entries" in data
assert len(data["entries"]) == 3
async def test_get_nonexistent_block(self, client):
"""Test getting a non-existent block."""
response = await client.get("/ledger/blocks/999")
assert response.status_code == 404
data = response.json()
assert "error" in data
assert "Block not found" in data["error"]
async def test_verify_chain_endpoint(self, client):
"""Test the verify chain endpoint."""
# Add some events
for i in range(3):
event_data = {
"event_type": "tool_call",
"actor_id": f"user{i}",
"action": f"action_{i}"
}
await client.post("/ledger/events", json=event_data)
# Verify the chain
response = await client.get("/ledger/verify-chain")
assert response.status_code == 200
data = response.json()
assert data["verified"] is True
assert "start_sequence" in data
assert "end_sequence" in data
async def test_verify_chain_with_range(self, client):
"""Test verify chain with specific range."""
# Add some events
for i in range(5):
event_data = {
"event_type": "tool_call",
"actor_id": f"user{i}",
"action": f"action_{i}"
}
await client.post("/ledger/events", json=event_data)
# Verify partial chain
response = await client.get("/ledger/verify-chain?start_sequence=2&end_sequence=4")
assert response.status_code == 200
data = response.json()
assert data["verified"] is True
assert data["start_sequence"] == 2
assert data["end_sequence"] == 4
async def test_get_merkle_proof_endpoint(self, client):
"""Test the get Merkle proof endpoint."""
# Add some events to create a block
for i in range(3):
event_data = {
"event_type": "tool_call",
"actor_id": f"user{i}",
"action": f"action_{i}"
}
await client.post("/ledger/events", json=event_data)
# Manually seal the block for testing
client.ledger.seal_current_block()
# Get Merkle proof for first entry
response = await client.get("/ledger/proof/1")
assert response.status_code == 200
data = response.json()
assert data["sequence_number"] == 1
assert "entry_hash" in data
assert "block_number" in data
assert "merkle_root" in data
assert "proof" in data
assert "leaf_hash" in data["proof"]
assert "path" in data["proof"]
assert "root_hash" in data["proof"]
assert data["verified"] is True
async def test_get_merkle_proof_nonexistent_entry(self, client):
"""Test getting Merkle proof for non-existent entry."""
response = await client.get("/ledger/proof/999")
assert response.status_code == 404
data = response.json()
assert "error" in data
assert "Entry not found" in data["error"]
async def test_get_ledger_statistics_endpoint(self, client):
"""Test the get ledger statistics endpoint."""
# Initially empty
response = await client.get("/ledger/statistics")
assert response.status_code == 200
data = response.json()
assert data["total_entries"] == 0
assert data["total_blocks"] == 0
assert data["current_sequence"] == 0
# Add some events
for i in range(3):
event_data = {
"event_type": "tool_call",
"actor_id": f"user{i}",
"action": f"action_{i}"
}
await client.post("/ledger/events", json=event_data)
# Check updated statistics
response = await client.get("/ledger/statistics")
assert response.status_code == 200
data = response.json()
assert data["total_entries"] == 3
assert data["total_blocks"] == 1
assert data["current_sequence"] == 3
async def test_multiple_event_types(self, client):
"""Test appending different event types."""
event_types = [
"tool_call",
"policy_decision",
"data_flow",
"contract_action",
"authentication",
"authorization",
"system_event"
]
for event_type in event_types:
event_data = {
"event_type": event_type,
"actor_id": "test_actor",
"action": "test_action"
}
response = await client.post("/ledger/events", json=event_data)
assert response.status_code == 201
async def test_event_with_complex_metadata(self, client):
"""Test event with complex metadata."""
event_data = {
"event_type": "tool_call",
"actor_id": "test_actor",
"action": "test_action",
"metadata": {
"nested": {"key": "value"},
"list": [1, 2, 3],
"boolean": True,
"null": None
}
}
response = await client.post("/ledger/events", json=event_data)
assert response.status_code == 201
data = response.json()
assert data["sequence_number"] == 1
# Verify the event was stored correctly
entry_response = await client.get("/ledger/entries/1")
assert entry_response.status_code == 200
entry_data = entry_response.json()
assert entry_data["event"]["metadata"] == event_data["metadata"]