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
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
193 lines
7.0 KiB
Python
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")
|