Files
9qeklajc ed9a3417d2 fix: give the Routstr top-up threshold an explicit unit
The auto top-up field has always been labelled "When credits are below (Sats)",
but the backend compared it as `balance >= threshold * 1000` against a balance
get_balance() had already converted to sats. Its sibling topup_amount_limit is
plain sats - it goes straight to send_token(amount, "sat", ...). So one settings
blob carried two units and the UI promised the one it did not use: an operator
asking to top up below 1000 sats got one below 1,000,000.

topup_threshold_sats says what it means and is compared as written. A legacy
topup_threshold keeps the thousandfold it has always been compared with, since
reinterpreting stored values as sats would drop the trigger point by a factor of
1000 on upgrade and leave a peer to run dry. A one-off warning per provider
names the sats value to migrate to.

The settings form now edits topup_threshold_sats, seeding it from the legacy
value times 1000 so the number shown is the number in force. Editing therefore
starts from the truth rather than silently moving the trigger on the next save,
and saving drops the legacy key so the stored blob carries one unit.

Validation accepts either key and rejects a blob carrying neither.

The PPQ path keeps its own topup_threshold, which is USD and unaffected.
2026-08-24 23:03:52 +02:00
..
2026-08-03 23:32:06 +02:00
2025-08-09 12:44:21 -03:00
2025-07-26 15:18:31 -04:00
fmt
2026-04-26 22:19:30 +02:00
2025-08-13 17:54:04 -03:00
2025-12-11 13:58:57 +08:00
2026-01-31 07:37:39 +08:00
2026-06-12 20:20:38 +02:00
2026-08-03 23:32:06 +02:00
2026-01-03 22:49:46 +01:00
2026-08-03 23:32:06 +02:00
2026-08-04 01:32:25 +02:00
2026-03-13 23:04:17 +01:00
2025-08-06 23:25:06 -03:00
2026-08-23 11:19:41 +02:00
2026-07-26 02:40:35 +02:00
2026-07-07 14:29:39 +02:00
2025-08-09 14:55:26 -03:00

Integration Tests

End-to-end tests for API endpoints, Cashu wallet operations, and database interactions.

Quick Start

# First-time setup (installs uv if needed)
make setup

# Check if all dependencies are installed
make check-deps

# Run tests
make test

Test Modes

The integration tests support two execution modes:

🎭 Mock Mode (Default - Fast)

  • Uses in-memory mocks for external services
  • No Docker required
  • Runs quickly, ideal for CI/CD
  • Good for rapid development iteration

🐳 Docker Mode (Realistic)

  • Uses real Docker services (Cashu mint, mock OpenAI, Nostr relay)
  • More accurate testing environment
  • Slower but catches more edge cases
  • Recommended before releases

Running Tests

Quick Mode (Mocked Services)

# All integration tests with mocks
pytest tests/integration/ -v

# Specific test file
pytest tests/integration/test_wallet_topup.py -v

# Skip slow tests
pytest tests/integration/ -m "not slow" -v

# Run only unit-style integration tests
pytest tests/integration/ -m "not requires_docker" -v

Full Integration Mode (Docker Services)

# Using the automated script (recommended)
./tests/run_integration.py

# Or manually:
docker-compose -f compose.testing.yml up -d
USE_LOCAL_SERVICES=1 pytest tests/integration/ -v
docker-compose -f compose.testing.yml down -v

CI/CD Mode

# Fast tests only for continuous integration
pytest tests/integration/ -m "not slow and not requires_docker" -v

# Performance tests
pytest tests/integration/ -m "performance" -v

Test Infrastructure

Core Fixtures

  • integration_client - Async HTTP client configured for testing
  • authenticated_client - Pre-authenticated client with API key
  • testmint_wallet - Mock/real Cashu wallet for token generation
  • db_snapshot - Database state tracking for verification
  • test_mode - Reports current execution mode (mock/docker)

Utility Classes

  • ResponseValidator - Validates API response formats
  • PerformanceValidator - Tracks and validates performance metrics
  • ConcurrencyTester - Tests concurrent request handling
  • CashuTokenGenerator - Generates valid/invalid test tokens

Environment Configuration

Test environment configuration is handled directly in conftest.py. The configuration automatically switches between:

  • Mock mode: Fast, uses mocked services (default)
  • Docker mode: Uses real Docker services when USE_LOCAL_SERVICES=1

This keeps all test configuration in one place and avoids file duplication.

Writing Tests

Basic Test Structure

@pytest.mark.integration
@pytest.mark.asyncio
async def test_wallet_topup(
    authenticated_client: AsyncClient,
    testmint_wallet: Any,
    db_snapshot: Any
):
    # Capture initial state
    await db_snapshot.capture()
    
    # Generate test token
    token = await testmint_wallet.mint_tokens(1000)
    
    # Make API request
    response = await authenticated_client.post(
        "/v1/wallet/topup", 
        params={"cashu_token": token}
    )
    
    # Validate response
    assert response.status_code == 200
    
    # Verify database changes
    diff = await db_snapshot.diff()
    assert len(diff["api_keys"]["modified"]) == 1

Testing Concurrent Operations

async def test_concurrent_topups(
    integration_client: AsyncClient,
    testmint_wallet: Any,
    create_api_key: Callable
):
    # Create multiple API keys
    keys = []
    for i in range(5):
        key, _ = await create_api_key(integration_client, testmint_wallet)
        keys.append(key)
    
    # Test concurrent requests
    tester = ConcurrencyTester()
    responses = await tester.run_concurrent_requests(
        integration_client,
        [{"method": "GET", "url": "/v1/wallet/", 
          "headers": {"Authorization": f"Bearer {key}"}} 
         for key in keys],
        max_concurrent=5
    )
    
    # All should succeed
    assert all(r.status_code == 200 for r in responses)

Performance Testing

@pytest.mark.performance
async def test_endpoint_performance(
    authenticated_client: AsyncClient,
    performance_validator: PerformanceValidator
):
    # Run multiple requests
    for i in range(100):
        start = performance_validator.start_timing("wallet_info")
        response = await authenticated_client.get("/v1/wallet/")
        performance_validator.end_timing("wallet_info", start)
    
    # Validate 95th percentile < 100ms
    result = performance_validator.validate_response_time(
        "wallet_info", max_duration=0.1, percentile=0.95
    )
    assert result["valid"], f"P95: {result['percentile_time']:.3f}s"

Troubleshooting

Tests Failing with Connection Errors

  • Ensure Docker services are running: docker ps
  • Check service logs: docker-compose -f compose.testing.yml logs
  • Verify ports aren't in use: lsof -i :3338,3000,8000,8088

Mock vs Docker Mode Confusion

  • Check current mode: Look for 🎭 or 🐳 emoji in test output
  • Force mock mode: Unset USE_LOCAL_SERVICES
  • Force Docker mode: export USE_LOCAL_SERVICES=1

Slow Test Execution

  • Use mock mode for development: pytest tests/integration/
  • Skip slow tests: pytest -m "not slow"
  • Run specific test files only
  • Use pytest-xdist for parallel execution: pytest -n auto

Installing uv Manually

If make dev-setup fails to install uv automatically:

# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or with pip
pip install uv

# Or with Homebrew
brew install uv

Best Practices

  1. Use Mock Mode for Development - It's fast and catches most issues
  2. Run Docker Mode Before PRs - Ensures realistic testing
  3. Add Appropriate Markers - Help others run relevant test subsets
    • Use @pytest.mark.slow for tests that take significant time (e.g., memory/load tests)
    • Use @pytest.mark.requires_docker for tests needing Docker services
  4. Verify Database State - Use db_snapshot for state verification
  5. Test Edge Cases - Invalid inputs, network failures, race conditions
  6. Monitor Performance - Add performance tests for critical paths