forked from Rockachopa/Timmy-time-dashboard
feat: complete Event Log, Ledger, Memory, Cascade Router, Upgrade Queue, Activity Feed
This commit implements six major features: 1. Event Log System (src/swarm/event_log.py) - SQLite-based audit trail for all swarm events - Task lifecycle tracking (created, assigned, completed, failed) - Agent lifecycle tracking (joined, left, status changes) - Integrated with coordinator for automatic logging - Dashboard page at /swarm/events 2. Lightning Ledger (src/lightning/ledger.py) - Transaction tracking for Lightning Network payments - Balance calculations (incoming, outgoing, net, available) - Integrated with payment_handler for automatic logging - Dashboard page at /lightning/ledger 3. Semantic Memory / Vector Store (src/memory/vector_store.py) - Embedding-based similarity search for Echo agent - Fallback to keyword matching if sentence-transformers unavailable - Personal facts storage and retrieval - Dashboard page at /memory 4. Cascade Router Integration (src/timmy/cascade_adapter.py) - Automatic LLM failover between providers (Ollama → AirLLM → API) - Circuit breaker pattern for failing providers - Metrics tracking per provider (latency, error rates) - Dashboard status page at /router/status 5. Self-Upgrade Approval Queue (src/upgrades/) - State machine for self-modifications: proposed → approved/rejected → applied/failed - Human approval required before applying changes - Git integration for branch management - Dashboard queue at /self-modify/queue 6. Real-Time Activity Feed (src/events/broadcaster.py) - WebSocket-based live activity streaming - Bridges event_log to dashboard clients - Activity panel on /swarm/live Tests: - 101 unit tests passing - 4 new E2E test files for Selenium testing - Run with: SELENIUM_UI=1 pytest tests/functional/ -v --headed Documentation: - 6 ADRs (017-022) documenting architecture decisions - Implementation summary in docs/IMPLEMENTATION_SUMMARY.md - Architecture diagram in docs/architecture-v2.md
This commit is contained in:
211
tests/test_ledger.py
Normal file
211
tests/test_ledger.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""Tests for Lightning ledger system."""
|
||||
|
||||
import pytest
|
||||
from lightning.ledger import (
|
||||
TransactionType,
|
||||
TransactionStatus,
|
||||
create_invoice_entry,
|
||||
record_outgoing_payment,
|
||||
mark_settled,
|
||||
mark_failed,
|
||||
get_by_hash,
|
||||
list_transactions,
|
||||
get_balance,
|
||||
get_transaction_stats,
|
||||
)
|
||||
|
||||
|
||||
class TestLedger:
|
||||
"""Test suite for Lightning ledger functionality."""
|
||||
|
||||
def test_create_invoice_entry(self):
|
||||
"""Test creating an incoming invoice entry."""
|
||||
entry = create_invoice_entry(
|
||||
payment_hash="test_hash_001",
|
||||
amount_sats=1000,
|
||||
memo="Test invoice",
|
||||
invoice="lnbc10u1...",
|
||||
source="test",
|
||||
task_id="task-123",
|
||||
agent_id="agent-456",
|
||||
)
|
||||
|
||||
assert entry.tx_type == TransactionType.INCOMING
|
||||
assert entry.status == TransactionStatus.PENDING
|
||||
assert entry.amount_sats == 1000
|
||||
assert entry.payment_hash == "test_hash_001"
|
||||
assert entry.memo == "Test invoice"
|
||||
assert entry.task_id == "task-123"
|
||||
assert entry.agent_id == "agent-456"
|
||||
|
||||
def test_record_outgoing_payment(self):
|
||||
"""Test recording an outgoing payment."""
|
||||
entry = record_outgoing_payment(
|
||||
payment_hash="test_hash_002",
|
||||
amount_sats=500,
|
||||
memo="Test payment",
|
||||
source="test",
|
||||
task_id="task-789",
|
||||
)
|
||||
|
||||
assert entry.tx_type == TransactionType.OUTGOING
|
||||
assert entry.status == TransactionStatus.PENDING
|
||||
assert entry.amount_sats == 500
|
||||
assert entry.payment_hash == "test_hash_002"
|
||||
|
||||
def test_mark_settled(self):
|
||||
"""Test marking a transaction as settled."""
|
||||
# Create invoice
|
||||
entry = create_invoice_entry(
|
||||
payment_hash="test_hash_settle",
|
||||
amount_sats=100,
|
||||
memo="To be settled",
|
||||
)
|
||||
assert entry.status == TransactionStatus.PENDING
|
||||
|
||||
# Mark as settled
|
||||
settled = mark_settled(
|
||||
payment_hash="test_hash_settle",
|
||||
preimage="preimage123",
|
||||
fee_sats=1,
|
||||
)
|
||||
|
||||
assert settled is not None
|
||||
assert settled.status == TransactionStatus.SETTLED
|
||||
assert settled.preimage == "preimage123"
|
||||
assert settled.fee_sats == 1
|
||||
assert settled.settled_at is not None
|
||||
|
||||
# Verify retrieval
|
||||
retrieved = get_by_hash("test_hash_settle")
|
||||
assert retrieved.status == TransactionStatus.SETTLED
|
||||
|
||||
def test_mark_failed(self):
|
||||
"""Test marking a transaction as failed."""
|
||||
# Create invoice
|
||||
entry = create_invoice_entry(
|
||||
payment_hash="test_hash_fail",
|
||||
amount_sats=200,
|
||||
memo="To fail",
|
||||
)
|
||||
|
||||
# Mark as failed
|
||||
failed = mark_failed("test_hash_fail", reason="Timeout")
|
||||
|
||||
assert failed is not None
|
||||
assert failed.status == TransactionStatus.FAILED
|
||||
assert "Timeout" in failed.memo
|
||||
|
||||
def test_get_by_hash_not_found(self):
|
||||
"""Test retrieving non-existent transaction."""
|
||||
result = get_by_hash("nonexistent_hash")
|
||||
assert result is None
|
||||
|
||||
def test_list_transactions_filtering(self):
|
||||
"""Test filtering transactions."""
|
||||
# Create various transactions
|
||||
create_invoice_entry("filter_test_1", 100, source="filter_test")
|
||||
create_invoice_entry("filter_test_2", 200, source="filter_test")
|
||||
|
||||
# Filter by type
|
||||
incoming = list_transactions(
|
||||
tx_type=TransactionType.INCOMING,
|
||||
limit=10,
|
||||
)
|
||||
assert all(t.tx_type == TransactionType.INCOMING for t in incoming)
|
||||
|
||||
# Filter by status
|
||||
pending = list_transactions(
|
||||
status=TransactionStatus.PENDING,
|
||||
limit=10,
|
||||
)
|
||||
assert all(t.status == TransactionStatus.PENDING for t in pending)
|
||||
|
||||
def test_get_balance(self):
|
||||
"""Test balance calculation."""
|
||||
# Get initial balance
|
||||
balance = get_balance()
|
||||
|
||||
assert "incoming_total_sats" in balance
|
||||
assert "outgoing_total_sats" in balance
|
||||
assert "net_sats" in balance
|
||||
assert isinstance(balance["incoming_total_sats"], int)
|
||||
assert isinstance(balance["outgoing_total_sats"], int)
|
||||
|
||||
def test_transaction_stats(self):
|
||||
"""Test transaction statistics."""
|
||||
# Create some transactions
|
||||
create_invoice_entry("stats_test_1", 100, source="stats_test")
|
||||
create_invoice_entry("stats_test_2", 200, source="stats_test")
|
||||
|
||||
# Get stats
|
||||
stats = get_transaction_stats(days=1)
|
||||
|
||||
# Should return dict with dates
|
||||
assert isinstance(stats, dict)
|
||||
# Stats structure depends on current date, just verify it's a dict
|
||||
|
||||
def test_unique_payment_hash(self):
|
||||
"""Test that payment hashes must be unique."""
|
||||
import sqlite3
|
||||
|
||||
hash_value = "unique_hash_test"
|
||||
|
||||
# First creation should succeed
|
||||
create_invoice_entry(hash_value, 100)
|
||||
|
||||
# Second creation with same hash should fail with IntegrityError
|
||||
with pytest.raises(sqlite3.IntegrityError):
|
||||
create_invoice_entry(hash_value, 200)
|
||||
|
||||
|
||||
class TestLedgerIntegration:
|
||||
"""Integration tests for ledger workflow."""
|
||||
|
||||
def test_full_invoice_lifecycle(self):
|
||||
"""Test complete invoice lifecycle: create -> settle."""
|
||||
# Create invoice
|
||||
entry = create_invoice_entry(
|
||||
payment_hash="lifecycle_test",
|
||||
amount_sats=5000,
|
||||
memo="Full lifecycle test",
|
||||
source="integration_test",
|
||||
)
|
||||
|
||||
assert entry.status == TransactionStatus.PENDING
|
||||
|
||||
# Mark as settled
|
||||
settled = mark_settled("lifecycle_test", preimage="secret_preimage")
|
||||
|
||||
assert settled.status == TransactionStatus.SETTLED
|
||||
assert settled.preimage == "secret_preimage"
|
||||
|
||||
# Verify in list
|
||||
transactions = list_transactions(limit=100)
|
||||
assert any(t.payment_hash == "lifecycle_test" for t in transactions)
|
||||
|
||||
# Verify balance reflects it
|
||||
balance = get_balance()
|
||||
# Balance should include this settled invoice
|
||||
|
||||
def test_outgoing_payment_lifecycle(self):
|
||||
"""Test complete outgoing payment lifecycle."""
|
||||
# Record outgoing payment
|
||||
entry = record_outgoing_payment(
|
||||
payment_hash="outgoing_test",
|
||||
amount_sats=300,
|
||||
memo="Outgoing payment",
|
||||
source="integration_test",
|
||||
)
|
||||
|
||||
assert entry.tx_type == TransactionType.OUTGOING
|
||||
|
||||
# Mark as settled (payment completed)
|
||||
settled = mark_settled(
|
||||
"outgoing_test",
|
||||
preimage="payment_proof",
|
||||
fee_sats=3,
|
||||
)
|
||||
|
||||
assert settled.fee_sats == 3
|
||||
assert settled.status == TransactionStatus.SETTLED
|
||||
Reference in New Issue
Block a user