From f6900d6bf6e493b1eb0c698880abbfe1014c64ad Mon Sep 17 00:00:00 2001 From: aitbc Date: Thu, 30 Apr 2026 11:57:09 +0200 Subject: [PATCH] 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 --- apps/api-gateway/pyproject.toml | 9 + apps/api-gateway/tests/__init__.py | 3 + apps/api-gateway/tests/test_gateway.py | 71 +++++ apps/docs/TEST_COVERAGE_REQUIREMENTS.md | 285 ++++++++++++++++++++ apps/governance-service/pyproject.toml | 10 + apps/governance-service/tests/__init__.py | 3 + apps/governance-service/tests/test_main.py | 72 +++++ apps/gpu-service/pyproject.toml | 10 + apps/gpu-service/tests/__init__.py | 3 + apps/gpu-service/tests/test_main.py | 47 ++++ apps/marketplace-service/pyproject.toml | 12 +- apps/marketplace-service/tests/__init__.py | 3 + apps/marketplace-service/tests/test_main.py | 56 ++++ apps/trading-service/pyproject.toml | 10 + apps/trading-service/tests/__init__.py | 3 + apps/trading-service/tests/test_main.py | 64 +++++ 16 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 apps/api-gateway/tests/__init__.py create mode 100644 apps/api-gateway/tests/test_gateway.py create mode 100644 apps/docs/TEST_COVERAGE_REQUIREMENTS.md create mode 100644 apps/governance-service/tests/__init__.py create mode 100644 apps/governance-service/tests/test_main.py create mode 100644 apps/gpu-service/tests/__init__.py create mode 100644 apps/gpu-service/tests/test_main.py create mode 100644 apps/marketplace-service/tests/__init__.py create mode 100644 apps/marketplace-service/tests/test_main.py create mode 100644 apps/trading-service/tests/__init__.py create mode 100644 apps/trading-service/tests/test_main.py diff --git a/apps/api-gateway/pyproject.toml b/apps/api-gateway/pyproject.toml index 7b21b260..135be1c8 100644 --- a/apps/api-gateway/pyproject.toml +++ b/apps/api-gateway/pyproject.toml @@ -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"] diff --git a/apps/api-gateway/tests/__init__.py b/apps/api-gateway/tests/__init__.py new file mode 100644 index 00000000..5f7fec42 --- /dev/null +++ b/apps/api-gateway/tests/__init__.py @@ -0,0 +1,3 @@ +""" +API Gateway tests +""" diff --git a/apps/api-gateway/tests/test_gateway.py b/apps/api-gateway/tests/test_gateway.py new file mode 100644 index 00000000..6c4b1f30 --- /dev/null +++ b/apps/api-gateway/tests/test_gateway.py @@ -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 diff --git a/apps/docs/TEST_COVERAGE_REQUIREMENTS.md b/apps/docs/TEST_COVERAGE_REQUIREMENTS.md new file mode 100644 index 00000000..cb411369 --- /dev/null +++ b/apps/docs/TEST_COVERAGE_REQUIREMENTS.md @@ -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/ +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 diff --git a/apps/governance-service/pyproject.toml b/apps/governance-service/pyproject.toml index 29d336fe..6f1f65ea 100644 --- a/apps/governance-service/pyproject.toml +++ b/apps/governance-service/pyproject.toml @@ -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"] diff --git a/apps/governance-service/tests/__init__.py b/apps/governance-service/tests/__init__.py new file mode 100644 index 00000000..4a94cc28 --- /dev/null +++ b/apps/governance-service/tests/__init__.py @@ -0,0 +1,3 @@ +""" +Governance service tests +""" diff --git a/apps/governance-service/tests/test_main.py b/apps/governance-service/tests/test_main.py new file mode 100644 index 00000000..4f73f20f --- /dev/null +++ b/apps/governance-service/tests/test_main.py @@ -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) diff --git a/apps/gpu-service/pyproject.toml b/apps/gpu-service/pyproject.toml index 78b68ddd..5bcdc9c6 100644 --- a/apps/gpu-service/pyproject.toml +++ b/apps/gpu-service/pyproject.toml @@ -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"] diff --git a/apps/gpu-service/tests/__init__.py b/apps/gpu-service/tests/__init__.py new file mode 100644 index 00000000..7aff24fd --- /dev/null +++ b/apps/gpu-service/tests/__init__.py @@ -0,0 +1,3 @@ +""" +GPU service tests +""" diff --git a/apps/gpu-service/tests/test_main.py b/apps/gpu-service/tests/test_main.py new file mode 100644 index 00000000..e02a8b92 --- /dev/null +++ b/apps/gpu-service/tests/test_main.py @@ -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 diff --git a/apps/marketplace-service/pyproject.toml b/apps/marketplace-service/pyproject.toml index 0dddfdfc..5254e3cf 100644 --- a/apps/marketplace-service/pyproject.toml +++ b/apps/marketplace-service/pyproject.toml @@ -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"] diff --git a/apps/marketplace-service/tests/__init__.py b/apps/marketplace-service/tests/__init__.py new file mode 100644 index 00000000..1d3571c2 --- /dev/null +++ b/apps/marketplace-service/tests/__init__.py @@ -0,0 +1,3 @@ +""" +Marketplace service tests +""" diff --git a/apps/marketplace-service/tests/test_main.py b/apps/marketplace-service/tests/test_main.py new file mode 100644 index 00000000..a057addc --- /dev/null +++ b/apps/marketplace-service/tests/test_main.py @@ -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) diff --git a/apps/trading-service/pyproject.toml b/apps/trading-service/pyproject.toml index bb68818e..380c136c 100644 --- a/apps/trading-service/pyproject.toml +++ b/apps/trading-service/pyproject.toml @@ -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"] diff --git a/apps/trading-service/tests/__init__.py b/apps/trading-service/tests/__init__.py new file mode 100644 index 00000000..974e7d88 --- /dev/null +++ b/apps/trading-service/tests/__init__.py @@ -0,0 +1,3 @@ +""" +Trading service tests +""" diff --git a/apps/trading-service/tests/test_main.py b/apps/trading-service/tests/test_main.py new file mode 100644 index 00000000..c848389d --- /dev/null +++ b/apps/trading-service/tests/test_main.py @@ -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)