Add pytest test files for microservices

- Created test files for GPU service (tests/test_main.py)
- Created test files for Marketplace service (tests/test_main.py)
- Created test files for Trading service (tests/test_main.py)
- Created test files for Governance service (tests/test_main.py)
- Created integration tests for API gateway (tests/test_gateway.py)
- Added pytest dependencies to all service pyproject.toml files
- Created TEST_COVERAGE_REQUIREMENTS.md documenting coverage targets and best practices

This completes Phase 8: Create pytest test files for microservices
This commit is contained in:
aitbc
2026-04-30 11:57:09 +02:00
parent 1315884dec
commit f6900d6bf6
16 changed files with 660 additions and 1 deletions

View File

@@ -13,6 +13,12 @@ dependencies = [
"aitbc-core",
]
[project.optional-dependencies]
test = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
@@ -21,3 +27,6 @@ build-backend = "poetry.core.masonry.api"
packages = [
{ include = "api_gateway", from = "src" }
]
[tool.poetry.extras]
test = ["pytest", "pytest-asyncio"]

View File

@@ -0,0 +1,3 @@
"""
API Gateway tests
"""

View File

@@ -0,0 +1,71 @@
"""
Test API Gateway routing
"""
import pytest
from fastapi.testclient import TestClient
from api_gateway.main import app
@pytest.fixture
def client():
"""Create test client for API Gateway"""
return TestClient(app)
def test_gateway_health_check(client):
"""Test gateway health check endpoint"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert data["service"] == "api-gateway"
def test_service_registry(client):
"""Test service registry endpoint"""
response = client.get("/services")
assert response.status_code == 200
data = response.json()
assert "services" in data
services = data["services"]
assert isinstance(services, list)
# Check that GPU service is registered
gpu_service = next((s for s in services if s["name"] == "gpu"), None)
assert gpu_service is not None
assert gpu_service["url"] == "http://localhost:8101"
assert "/gpu/*" in gpu_service["routes"]
def test_gpu_route_proxy(client):
"""Test that gateway proxies requests to GPU service"""
# This test requires GPU service to be running
# In CI, this would be mocked or services would be started
response = client.get("/gpu/health")
# May fail if service not running, but tests routing logic
assert response.status_code in [200, 503] # 503 if service down
def test_marketplace_route_proxy(client):
"""Test that gateway proxies requests to Marketplace service"""
response = client.get("/marketplace/health")
assert response.status_code in [200, 503]
def test_trading_route_proxy(client):
"""Test that gateway proxies requests to Trading service"""
response = client.get("/trading/health")
assert response.status_code in [200, 503]
def test_governance_route_proxy(client):
"""Test that gateway proxies requests to Governance service"""
response = client.get("/governance/health")
assert response.status_code in [200, 503]
def test_unknown_route(client):
"""Test that unknown routes return 404"""
response = client.get("/unknown/path")
assert response.status_code == 404

View File

@@ -0,0 +1,285 @@
# AITBC Microservices Test Coverage Requirements
This document outlines the test coverage requirements for the AITBC microservices architecture.
## Overview
Each microservice should maintain adequate test coverage to ensure reliability and facilitate future development. Test coverage should be measured and tracked as part of the CI/CD pipeline.
## Test Coverage Targets
### Minimum Requirements
- **Unit Tests**: 70% line coverage minimum
- **Integration Tests**: 50% line coverage minimum
- **Overall Coverage**: 60% line coverage minimum per service
### Ideal Targets
- **Unit Tests**: 80% line coverage
- **Integration Tests**: 70% line coverage
- **Overall Coverage**: 75% line coverage per service
## Service-Specific Requirements
### GPU Service
**Unit Test Coverage Requirements:**
- Domain models (GPUArchitecture, GPURegistry, ConsumerGPUProfile, EdgeGPUMetrics, GPUBooking, GPUReview): 80%
- Service layer (EdgeGPUService): 75%
- API endpoints: 70%
**Integration Test Coverage Requirements:**
- Database operations: 70%
- API endpoint integration: 60%
- Service dependencies: 50%
**Critical Path Tests:**
- Consumer GPU profile listing
- Edge GPU metrics creation
- GPU discovery (async)
- Inference optimization (async)
### Marketplace Service
**Unit Test Coverage Requirements:**
- Domain models (MarketplaceOffer, MarketplaceBid, GlobalMarketplaceOffer, GlobalMarketplaceTransaction, etc.): 75%
- Service layer (MarketplaceService): 70%
- API endpoints: 65%
**Integration Test Coverage Requirements:**
- Database operations: 70%
- API endpoint integration: 60%
- Service dependencies: 50%
**Critical Path Tests:**
- Offer creation and retrieval
- Bid creation and retrieval
- Marketplace analytics
### Trading Service
**Unit Test Coverage Requirements:**
- Domain models (TradeRequest, TradeMatch, TradeNegotiation, TradeAgreement, TradeSettlement, TradeFeedback, TradingAnalytics): 75%
- Service layer (TradingService): 70%
- API endpoints: 65%
**Integration Test Coverage Requirements:**
- Database operations: 70%
- API endpoint integration: 60%
- Service dependencies: 50%
**Critical Path Tests:**
- Trade request creation
- Trade matching logic
- Agreement creation
- Settlement processing
### Governance Service
**Unit Test Coverage Requirements:**
- Domain models (GovernanceProfile, Proposal, Vote, DaoTreasury, TransparencyReport): 75%
- Service layer (GovernanceService): 70%
- API endpoints: 65%
**Integration Test Coverage Requirements:**
- Database operations: 70%
- API endpoint integration: 60%
- Service dependencies: 50%
**Critical Path Tests:**
- Proposal creation
- Vote casting
- Proposal execution
- Treasury management
### API Gateway
**Unit Test Coverage Requirements:**
- Routing logic: 80%
- Service registry: 75%
- Proxy functionality: 70%
**Integration Test Coverage Requirements:**
- Gateway to service routing: 70%
- Load balancing: 50%
- Error handling: 60%
**Critical Path Tests:**
- Health check routing
- Service registry updates
- Request proxying to each service
- Unknown route handling
## Test Types
### Unit Tests
**Purpose**: Test individual functions and methods in isolation.
**Requirements**:
- Mock external dependencies (database, external APIs)
- Test edge cases and error conditions
- Test validation logic
- Test business logic
**Example Coverage Areas**:
- Domain model validation
- Service method logic
- Utility functions
- Helper methods
### Integration Tests
**Purpose**: Test interactions between components.
**Requirements**:
- Use test databases
- Test database operations
- Test API endpoint integration
- Test service-to-service communication
**Example Coverage Areas**:
- Database CRUD operations
- API request/response handling
- Service dependency resolution
- Transaction management
### End-to-End Tests
**Purpose**: Test complete workflows across services.
**Requirements**:
- Test critical user journeys
- Test gateway routing
- Test service orchestration
- Test error propagation
**Example Coverage Areas**:
- Complete trade flow (request → match → negotiate → agree → settle)
- Complete proposal flow (create → vote → execute)
- Gateway routing to all services
## Test Execution
### Running Tests
**Run all tests for a service:**
```bash
cd apps/<service-name>
pytest tests/
```
**Run with coverage:**
```bash
pytest tests/ --cov=src --cov-report=html --cov-report=term
```
**Run specific test file:**
```bash
pytest tests/test_main.py
```
**Run specific test:**
```bash
pytest tests/test_main.py::test_health_check
```
### CI/CD Integration
Tests should run automatically in CI/CD pipeline:
1. **On Pull Request**: Run all tests with coverage reporting
2. **On Merge to Main**: Run all tests with coverage reporting and enforce minimum coverage
3. **Nightly**: Run full test suite including end-to-end tests
### Coverage Enforcement
Minimum coverage thresholds should be enforced in CI:
```ini
[tool.coverage.run]
source = "src"
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"@abstractmethod",
]
[tool.coverage.fail_under]
minimum = 60
```
## Coverage Reporting
### Coverage Reports
Coverage reports should be generated in multiple formats:
- **HTML**: For detailed analysis (coverage/html/index.html)
- **Terminal**: For quick summary
- **XML**: For CI/CD integration
### Coverage Tracking
Coverage should be tracked over time to identify trends:
- Track coverage by service
- Track coverage by module
- Track coverage by test type
- Identify declining coverage
## Best Practices
### Test Organization
- Organize tests by module/functionality
- Use descriptive test names
- Keep tests independent
- Use fixtures for common setup
### Test Data
- Use factories for test data creation
- Use in-memory databases for unit tests
- Use test database for integration tests
- Clean up test data after each test
### Mocking
- Mock external dependencies
- Use pytest-mock for mocking
- Mock database calls in unit tests
- Use real database in integration tests
### Async Testing
- Use pytest-asyncio for async tests
- Mark async tests with @pytest.mark.asyncio
- Test async service methods
- Test async database operations
## Coverage Exclusions
The following can be excluded from coverage calculations:
- Type checking blocks (`if TYPE_CHECKING:`)
- Abstract methods
- Representation methods (`__repr__`)
- Test files themselves
- Configuration files
- Migration scripts
## Next Steps
1. Add coverage reporting to CI/CD pipeline
2. Set up coverage tracking dashboard
3. Review coverage reports regularly
4. Increase coverage targets as codebase matures
5. Add end-to-end tests for critical workflows

View File

@@ -14,6 +14,13 @@ dependencies = [
"aitbc-core",
]
[project.optional-dependencies]
test = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"httpx>=0.25.0",
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
@@ -22,3 +29,6 @@ build-backend = "poetry.core.masonry.api"
packages = [
{ include = "governance_service", from = "src" }
]
[tool.poetry.extras]
test = ["pytest", "pytest-asyncio", "httpx"]

View File

@@ -0,0 +1,3 @@
"""
Governance service tests
"""

View File

@@ -0,0 +1,72 @@
"""
Test Governance service main application
"""
import pytest
from fastapi.testclient import TestClient
from governance_service.main import app
@pytest.fixture
def client():
"""Create test client for Governance service"""
return TestClient(app)
def test_health_check(client):
"""Test health check endpoint"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert data["service"] == "governance-service"
def test_governance_status(client):
"""Test governance status endpoint"""
response = client.get("/governance/status")
assert response.status_code == 200
data = response.json()
assert data["status"] == "operational"
assert data["service"] == "governance-service"
def test_get_governance_profiles(client):
"""Test get governance profiles endpoint"""
response = client.get("/v1/governance/profiles")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_governance_proposals(client):
"""Test get governance proposals endpoint"""
response = client.get("/v1/governance/proposals")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_governance_votes(client):
"""Test get governance votes endpoint"""
response = client.get("/v1/governance/votes")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_governance_treasury(client):
"""Test get governance treasury endpoint"""
response = client.get("/v1/governance/treasury")
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)
def test_get_governance_analytics(client):
"""Test get governance analytics endpoint"""
response = client.get("/v1/governance/analytics")
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)

View File

@@ -14,6 +14,13 @@ dependencies = [
"aitbc-core",
]
[project.optional-dependencies]
test = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"httpx>=0.25.0",
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
@@ -22,3 +29,6 @@ build-backend = "poetry.core.masonry.api"
packages = [
{ include = "gpu_service", from = "src" }
]
[tool.poetry.extras]
test = ["pytest", "pytest-asyncio", "httpx"]

View File

@@ -0,0 +1,3 @@
"""
GPU service tests
"""

View File

@@ -0,0 +1,47 @@
"""
Test GPU service main application
"""
import pytest
from fastapi.testclient import TestClient
from gpu_service.main import app
@pytest.fixture
def client():
"""Create test client for GPU service"""
return TestClient(app)
def test_health_check(client):
"""Test health check endpoint"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert data["service"] == "gpu-service"
def test_gpu_status(client):
"""Test GPU status endpoint"""
response = client.get("/gpu/status")
assert response.status_code == 200
data = response.json()
assert data["status"] == "operational"
assert data["service"] == "gpu-service"
def test_get_consumer_gpu_profiles(client):
"""Test get consumer GPU profiles endpoint"""
response = client.get("/v1/marketplace/edge-gpu/profiles")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
# Check that at least one profile is returned
assert len(data) > 0
# Check that profile has required fields
profile = data[0]
assert "profile_id" in profile
assert "name" in profile
assert "architecture" in profile

View File

@@ -1,7 +1,7 @@
[project]
name = "marketplace-service"
version = "0.1.0"
description = "AITBC Marketplace Service for GPU marketplace operations"
description = "AITBC Marketplace Service for marketplace operations"
authors = [
{name = "AITBC Team", email = "team@aitbc.dev"}
]
@@ -14,6 +14,13 @@ dependencies = [
"aitbc-core",
]
[project.optional-dependencies]
test = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"httpx>=0.25.0",
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
@@ -22,3 +29,6 @@ build-backend = "poetry.core.masonry.api"
packages = [
{ include = "marketplace_service", from = "src" }
]
[tool.poetry.extras]
test = ["pytest", "pytest-asyncio", "httpx"]

View File

@@ -0,0 +1,3 @@
"""
Marketplace service tests
"""

View File

@@ -0,0 +1,56 @@
"""
Test Marketplace service main application
"""
import pytest
from fastapi.testclient import TestClient
from marketplace_service.main import app
@pytest.fixture
def client():
"""Create test client for Marketplace service"""
return TestClient(app)
def test_health_check(client):
"""Test health check endpoint"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert data["service"] == "marketplace-service"
def test_marketplace_status(client):
"""Test marketplace status endpoint"""
response = client.get("/marketplace/status")
assert response.status_code == 200
data = response.json()
assert data["status"] == "operational"
assert data["service"] == "marketplace-service"
def test_get_marketplace_offers(client):
"""Test get marketplace offers endpoint"""
response = client.get("/v1/marketplace/offers")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_marketplace_bids(client):
"""Test get marketplace bids endpoint"""
response = client.get("/v1/marketplace/bids")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_marketplace_analytics(client):
"""Test get marketplace analytics endpoint"""
response = client.get("/v1/marketplace/analytics")
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)

View File

@@ -14,6 +14,13 @@ dependencies = [
"aitbc-core",
]
[project.optional-dependencies]
test = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"httpx>=0.25.0",
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
@@ -22,3 +29,6 @@ build-backend = "poetry.core.masonry.api"
packages = [
{ include = "trading_service", from = "src" }
]
[tool.poetry.extras]
test = ["pytest", "pytest-asyncio", "httpx"]

View File

@@ -0,0 +1,3 @@
"""
Trading service tests
"""

View File

@@ -0,0 +1,64 @@
"""
Test Trading service main application
"""
import pytest
from fastapi.testclient import TestClient
from trading_service.main import app
@pytest.fixture
def client():
"""Create test client for Trading service"""
return TestClient(app)
def test_health_check(client):
"""Test health check endpoint"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert data["service"] == "trading-service"
def test_trading_status(client):
"""Test trading status endpoint"""
response = client.get("/trading/status")
assert response.status_code == 200
data = response.json()
assert data["status"] == "operational"
assert data["service"] == "trading-service"
def test_get_trade_requests(client):
"""Test get trade requests endpoint"""
response = client.get("/v1/trading/requests")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_trade_matches(client):
"""Test get trade matches endpoint"""
response = client.get("/v1/trading/matches")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_trade_agreements(client):
"""Test get trade agreements endpoint"""
response = client.get("/v1/trading/agreements")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_get_trading_analytics(client):
"""Test get trading analytics endpoint"""
response = client.get("/v1/trading/analytics")
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)