Files
aitbc/apps/pool-hub/tests/test_billing_integration.py
aitbc e22d864944
Some checks failed
API Endpoint Tests / test-api-endpoints (push) Successful in 11s
CLI Tests / test-cli (push) Failing after 7s
Documentation Validation / validate-docs (push) Successful in 8s
Documentation Validation / validate-policies-strict (push) Successful in 3s
Integration Tests / test-service-integration (push) Successful in 38s
Python Tests / test-python (push) Successful in 11s
Security Scanning / security-scan (push) Successful in 29s
Multi-Node Blockchain Health Monitoring / health-check (push) Successful in 1s
feat: implement CLI blockchain features and pool hub enhancements
CLI Blockchain Features:
- Added block operations: import, export, import-chain, blocks-range
- Added messaging system commands (deploy, state, topics, create-topic, messages, post, vote, search, reputation, moderate)
- Added network force-sync operation
- Replaced marketplace handlers with actual RPC calls
- Replaced AI handlers with actual RPC calls
- Added account operations (account get)
- Added transaction query operations
- Added mempool query operations
- Created keystore_auth.py for authentication
- Removed extended features interception
- All handlers use keystore credentials for authenticated endpoints

Pool Hub Enhancements:
- Added SLA monitoring and capacity tables
- Added billing integration service
- Added SLA collector service
- Added SLA router endpoints
- Updated pool hub models and settings
- Added integration tests for billing and SLA
- Updated documentation with SLA monitoring guide
2026-04-22 15:59:00 +02:00

193 lines
7.0 KiB
Python

"""
Tests for Billing Integration Service
"""
import pytest
from datetime import datetime, timedelta
from decimal import Decimal
from unittest.mock import AsyncMock, patch
from sqlalchemy.orm import Session
from poolhub.models import Miner, MatchRequest, MatchResult
from poolhub.services.billing_integration import BillingIntegration
@pytest.fixture
def billing_integration(db_session: Session) -> BillingIntegration:
"""Create billing integration fixture"""
return BillingIntegration(db_session)
@pytest.fixture
def sample_miner(db_session: Session) -> Miner:
"""Create sample miner fixture"""
miner = Miner(
miner_id="test_miner_001",
api_key_hash="hash123",
addr="127.0.0.1:8080",
proto="http",
gpu_vram_gb=24.0,
gpu_name="RTX 4090",
cpu_cores=16,
ram_gb=64.0,
max_parallel=4,
base_price=0.50,
)
db_session.add(miner)
db_session.commit()
return miner
@pytest.mark.asyncio
async def test_record_usage(billing_integration: BillingIntegration):
"""Test recording usage data"""
# Mock the HTTP client
with patch("poolhub.services.billing_integration.httpx.AsyncClient") as mock_client:
mock_response = AsyncMock()
mock_response.json.return_value = {"status": "success", "id": "usage_123"}
mock_response.raise_for_status = AsyncMock()
mock_client.return_value.__aenter__.return_value.post = AsyncMock(return_value=mock_response)
result = await billing_integration.record_usage(
tenant_id="tenant_001",
resource_type="gpu_hours",
quantity=Decimal("10.5"),
unit_price=Decimal("0.50"),
job_id="job_123",
)
assert result["status"] == "success"
@pytest.mark.asyncio
async def test_record_usage_with_fallback_pricing(billing_integration: BillingIntegration):
"""Test recording usage with fallback pricing when unit_price not provided"""
with patch("poolhub.services.billing_integration.httpx.AsyncClient") as mock_client:
mock_response = AsyncMock()
mock_response.json.return_value = {"status": "success", "id": "usage_123"}
mock_response.raise_for_status = AsyncMock()
mock_client.return_value.__aenter__.return_value.post = AsyncMock(return_value=mock_response)
result = await billing_integration.record_usage(
tenant_id="tenant_001",
resource_type="gpu_hours",
quantity=Decimal("10.5"),
# unit_price not provided
)
assert result["status"] == "success"
@pytest.mark.asyncio
async def test_sync_miner_usage(billing_integration: BillingIntegration, sample_miner: Miner):
"""Test syncing usage for a specific miner"""
end_date = datetime.utcnow()
start_date = end_date - timedelta(hours=24)
with patch("poolhub.services.billing_integration.httpx.AsyncClient") as mock_client:
mock_response = AsyncMock()
mock_response.json.return_value = {"status": "success", "id": "usage_123"}
mock_response.raise_for_status = AsyncMock()
mock_client.return_value.__aenter__.return_value.post = AsyncMock(return_value=mock_response)
result = await billing_integration.sync_miner_usage(
miner_id=sample_miner.miner_id,
start_date=start_date,
end_date=end_date,
)
assert result["miner_id"] == sample_miner.miner_id
assert result["tenant_id"] == sample_miner.miner_id
assert "usage_records" in result
@pytest.mark.asyncio
async def test_sync_all_miners_usage(billing_integration: BillingIntegration, sample_miner: Miner):
"""Test syncing usage for all miners"""
with patch("poolhub.services.billing_integration.httpx.AsyncClient") as mock_client:
mock_response = AsyncMock()
mock_response.json.return_value = {"status": "success", "id": "usage_123"}
mock_response.raise_for_status = AsyncMock()
mock_client.return_value.__aenter__.return_value.post = AsyncMock(return_value=mock_response)
result = await billing_integration.sync_all_miners_usage(hours_back=24)
assert result["miners_processed"] >= 1
assert "total_usage_records" in result
def test_collect_miner_usage(billing_integration: BillingIntegration, sample_miner: Miner):
"""Test collecting usage data for a miner"""
end_date = datetime.utcnow()
start_date = end_date - timedelta(hours=24)
usage_data = billing_integration.db.run_sync(
lambda sess: billing_integration._collect_miner_usage(
sample_miner.miner_id, start_date, end_date
)
)
assert "gpu_hours" in usage_data
assert "api_calls" in usage_data
assert "compute_hours" in usage_data
@pytest.mark.asyncio
async def test_get_billing_metrics(billing_integration: BillingIntegration):
"""Test getting billing metrics from coordinator-api"""
with patch("poolhub.services.billing_integration.httpx.AsyncClient") as mock_client:
mock_response = AsyncMock()
mock_response.json.return_value = {
"totals": {"cost": 100.0, "records": 50},
"by_resource": {"gpu_hours": {"cost": 50.0}},
}
mock_response.raise_for_status = AsyncMock()
mock_client.return_value.__aenter__.return_value.get = AsyncMock(return_value=mock_response)
metrics = await billing_integration.get_billing_metrics(hours=24)
assert "totals" in metrics
@pytest.mark.asyncio
async def test_trigger_invoice_generation(billing_integration: BillingIntegration):
"""Test triggering invoice generation"""
with patch("poolhub.services.billing_integration.httpx.AsyncClient") as mock_client:
mock_response = AsyncMock()
mock_response.json.return_value = {
"invoice_number": "INV-001",
"status": "draft",
"total_amount": 100.0,
}
mock_response.raise_for_status = AsyncMock()
mock_client.return_value.__aenter__.return_value.post = AsyncMock(return_value=mock_response)
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=30)
result = await billing_integration.trigger_invoice_generation(
tenant_id="tenant_001",
period_start=start_date,
period_end=end_date,
)
assert result["invoice_number"] == "INV-001"
def test_resource_type_mapping(billing_integration: BillingIntegration):
"""Test resource type mapping"""
assert "gpu_hours" in billing_integration.resource_type_mapping
assert "storage_gb" in billing_integration.resource_type_mapping
def test_fallback_pricing(billing_integration: BillingIntegration):
"""Test fallback pricing configuration"""
assert "gpu_hours" in billing_integration.fallback_pricing
assert billing_integration.fallback_pricing["gpu_hours"]["unit_price"] == Decimal("0.50")