chore: Add test reports directory creation step in CI workflows
Some checks failed
Docker Build and Push / build-and-push (push) Failing after 36s
LabFusion CI/CD Pipeline / api-gateway (push) Failing after 1m11s
Integration Tests / integration-tests (push) Failing after 29s
LabFusion CI/CD Pipeline / api-docs (push) Successful in 1m42s
Integration Tests / performance-tests (push) Has been skipped
Service Adapters (Python FastAPI) / test (3.1) (push) Failing after 11s
Service Adapters (Python FastAPI) / test (3.11) (push) Failing after 19s
LabFusion CI/CD Pipeline / frontend (push) Failing after 1m50s
LabFusion CI/CD Pipeline / integration-tests (push) Has been skipped
Service Adapters (Python FastAPI) / test (3.12) (push) Failing after 20s
Service Adapters (Python FastAPI) / test (3.9) (push) Failing after 21s
Service Adapters (Python FastAPI) / build (push) Has been skipped
LabFusion CI/CD Pipeline / service-adapters (push) Failing after 20s

### Summary of Changes
- Introduced a step to create a `tests/reports` directory in both CI workflows for Service Adapters and the main CI configuration.
- This ensures that test reports have a designated location for output, improving organization and accessibility.

### Expected Results
- Enhanced structure for test report generation, facilitating easier access to test results and improving overall CI workflow clarity.
This commit is contained in:
GSRN
2025-09-15 21:03:17 +02:00
parent 22f806f6fa
commit 64d4e405c5
10 changed files with 589 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--tb=short
--strict-markers
--disable-warnings
--cov=.
--cov-report=term-missing
--cov-report=html
--cov-report=xml
--junitxml=tests/reports/junit.xml
markers =
unit: Unit tests
integration: Integration tests
slow: Slow running tests

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""
Test runner script for LabFusion Service Adapters
"""
import subprocess
import sys
import os
def run_tests():
"""Run the test suite"""
print("🧪 Running LabFusion Service Adapters Tests")
print("=" * 50)
# Ensure test reports directory exists
os.makedirs("tests/reports", exist_ok=True)
# Run pytest with coverage
cmd = [
"pytest",
"tests/",
"-v",
"--cov=.",
"--cov-report=term-missing",
"--cov-report=html",
"--cov-report=xml",
"--junitxml=tests/reports/junit.xml",
"--tb=short"
]
print(f"Running: {' '.join(cmd)}")
print()
result = subprocess.run(cmd, cwd=os.path.dirname(__file__))
if result.returncode == 0:
print("\n✅ All tests passed!")
else:
print("\n❌ Some tests failed!")
sys.exit(1)
if __name__ == "__main__":
run_tests()

View File

@@ -0,0 +1 @@
# Test package for LabFusion Service Adapters

View File

@@ -0,0 +1,106 @@
"""
Pytest configuration and fixtures for service adapters tests
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch
from main import app
@pytest.fixture
def client():
"""Provide a test client for the FastAPI app"""
return TestClient(app)
@pytest.fixture
def mock_services_config():
"""Mock services configuration for testing"""
return {
"home_assistant": {
"enabled": True,
"url": "http://homeassistant.local:8123",
"token": "test_token"
},
"frigate": {
"enabled": True,
"url": "http://frigate.local:5000"
},
"immich": {
"enabled": False,
"url": "http://immich.local:2283",
"api_key": "test_key"
}
}
@pytest.fixture
def sample_ha_entities():
"""Sample Home Assistant entities for testing"""
return {
"sensor.temperature": {
"entity_id": "sensor.temperature",
"state": "22.5",
"attributes": {
"unit_of_measurement": "°C",
"friendly_name": "Temperature"
}
},
"light.living_room": {
"entity_id": "light.living_room",
"state": "on",
"attributes": {
"friendly_name": "Living Room Light"
}
}
}
@pytest.fixture
def sample_frigate_events():
"""Sample Frigate events for testing"""
return {
"events": [
{
"id": "event_123",
"timestamp": "2024-01-01T12:00:00Z",
"camera": "front_door",
"label": "person",
"confidence": 0.95
},
{
"id": "event_456",
"timestamp": "2024-01-01T12:05:00Z",
"camera": "back_yard",
"label": "car",
"confidence": 0.87
}
]
}
@pytest.fixture
def sample_immich_assets():
"""Sample Immich assets for testing"""
return {
"assets": [
{
"id": "asset_123",
"filename": "IMG_20240101_120000.jpg",
"created_at": "2024-01-01T12:00:00Z",
"tags": ["family", "vacation"],
"faces": ["person_1", "person_2"]
}
]
}
@pytest.fixture(autouse=True)
def mock_redis():
"""Mock Redis client for all tests"""
with patch('services.redis_client.redis_client') as mock_redis:
mock_redis.ping.return_value = True
mock_redis.set.return_value = True
mock_redis.get.return_value = None
yield mock_redis

View File

@@ -0,0 +1,87 @@
"""
Tests for general API routes
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch
from main import app
client = TestClient(app)
class TestGeneralRoutes:
"""Test general API routes"""
def test_root_endpoint(self):
"""Test the root endpoint"""
response = client.get("/")
assert response.status_code == 200
data = response.json()
assert data["message"] == "LabFusion Service Adapters API"
assert data["version"] == "1.0.0"
def test_health_check(self):
"""Test the health check endpoint"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert "timestamp" in data
# Verify timestamp is in ISO format
assert "T" in data["timestamp"] or "Z" in data["timestamp"]
@patch('services.config.SERVICES')
def test_get_services(self, mock_services):
"""Test the get services endpoint"""
# Mock the services configuration
mock_services.items.return_value = [
("home_assistant", {
"enabled": True,
"url": "http://homeassistant.local:8123"
}),
("frigate", {
"enabled": True,
"url": "http://frigate.local:5000"
}),
("immich", {
"enabled": False,
"url": "http://immich.local:2283"
})
]
response = client.get("/services")
assert response.status_code == 200
data = response.json()
assert "home_assistant" in data
assert "frigate" in data
assert "immich" in data
# Check service status structure
ha_service = data["home_assistant"]
assert ha_service["enabled"] is True
assert ha_service["url"] == "http://homeassistant.local:8123"
assert ha_service["status"] == "unknown"
def test_health_check_response_model(self):
"""Test that health check returns proper response model"""
response = client.get("/health")
data = response.json()
# Verify all required fields are present
required_fields = ["status", "timestamp"]
for field in required_fields:
assert field in data
def test_root_response_model(self):
"""Test that root endpoint returns proper response model"""
response = client.get("/")
data = response.json()
# Verify all required fields are present
required_fields = ["message", "version"]
for field in required_fields:
assert field in data

View File

@@ -0,0 +1,94 @@
"""
Tests for Home Assistant routes
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, AsyncMock
from main import app
client = TestClient(app)
class TestHomeAssistantRoutes:
"""Test Home Assistant API routes"""
@patch('routes.home_assistant.httpx.AsyncClient')
async def test_get_entities_success(self, mock_client_class):
"""Test successful retrieval of Home Assistant entities"""
# Mock the HTTP client response
mock_response = AsyncMock()
mock_response.json.return_value = {
"sensor.temperature": {
"entity_id": "sensor.temperature",
"state": "22.5",
"attributes": {
"unit_of_measurement": "°C",
"friendly_name": "Temperature"
}
},
"light.living_room": {
"entity_id": "light.living_room",
"state": "on",
"attributes": {
"friendly_name": "Living Room Light"
}
}
}
mock_response.status_code = 200
mock_client = AsyncMock()
mock_client.get.return_value = mock_response
mock_client_class.return_value.__aenter__.return_value = mock_client
response = client.get("/home-assistant/entities")
assert response.status_code == 200
data = response.json()
assert "entities" in data
assert len(data["entities"]) == 2
# Check first entity
temp_entity = data["entities"][0]
assert temp_entity["entity_id"] == "sensor.temperature"
assert temp_entity["state"] == "22.5"
assert temp_entity["attributes"]["unit_of_measurement"] == "°C"
@patch('routes.home_assistant.httpx.AsyncClient')
async def test_get_entities_api_error(self, mock_client_class):
"""Test handling of Home Assistant API errors"""
# Mock HTTP error response
mock_response = AsyncMock()
mock_response.status_code = 500
mock_response.text = "Internal Server Error"
mock_client = AsyncMock()
mock_client.get.return_value = mock_response
mock_client_class.return_value.__aenter__.return_value = mock_client
response = client.get("/home-assistant/entities")
assert response.status_code == 500
@patch('routes.home_assistant.httpx.AsyncClient')
async def test_get_entities_connection_error(self, mock_client_class):
"""Test handling of connection errors"""
# Mock connection error
mock_client_class.return_value.__aenter__.side_effect = Exception("Connection failed")
response = client.get("/home-assistant/entities")
assert response.status_code == 500
def test_get_entities_endpoint_exists(self):
"""Test that the entities endpoint exists"""
# This will fail if the route doesn't exist, but we can't test the actual
# functionality without mocking the Home Assistant API
response = client.get("/home-assistant/entities")
# Should return either 200 (success) or 500 (API error)
assert response.status_code in [200, 500]
def test_home_assistant_routes_available(self):
"""Test that Home Assistant routes are available"""
# Check that the router is included by testing a known endpoint
response = client.get("/home-assistant/entities")
# Should not return 404 (route not found)
assert response.status_code != 404

View File

@@ -0,0 +1,49 @@
"""
Tests for the main FastAPI application
"""
import pytest
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
class TestMainApp:
"""Test the main FastAPI application"""
def test_app_creation(self):
"""Test that the FastAPI app is created correctly"""
assert app is not None
assert app.title == "LabFusion Service Adapters"
assert app.version == "1.0.0"
def test_cors_middleware(self):
"""Test that CORS middleware is properly configured"""
# Test a simple request to verify CORS headers
response = client.get("/")
assert response.status_code == 200
# CORS headers should be present
assert "access-control-allow-origin" in response.headers
def test_routers_included(self):
"""Test that all routers are included"""
# Check that all expected routes are available
routes = [route.path for route in app.routes]
# General routes
assert "/" in routes
assert "/health" in routes
assert "/services" in routes
# Other service routes should be included
# (exact paths depend on router definitions)
def test_openapi_docs(self):
"""Test that OpenAPI documentation is available"""
response = client.get("/docs")
assert response.status_code == 200
response = client.get("/openapi.json")
assert response.status_code == 200
assert "openapi" in response.json()

View File

@@ -0,0 +1,181 @@
"""
Tests for Pydantic models and schemas
"""
import pytest
from datetime import datetime
from typing import Dict, Any
from models.schemas import (
ServiceStatus, HAAttributes, HAEntity, HAEntitiesResponse,
FrigateEvent, FrigateEventsResponse, ImmichAsset, ImmichAssetsResponse,
EventData, EventResponse, Event, EventsResponse,
HealthResponse, RootResponse
)
class TestServiceStatus:
"""Test ServiceStatus model"""
def test_service_status_creation(self):
"""Test creating a ServiceStatus instance"""
service = ServiceStatus(
enabled=True,
url="http://example.com",
status="healthy"
)
assert service.enabled is True
assert service.url == "http://example.com"
assert service.status == "healthy"
def test_service_status_validation(self):
"""Test ServiceStatus validation"""
# Valid data
service = ServiceStatus(
enabled=False,
url="https://api.example.com",
status="unhealthy"
)
assert service.enabled is False
def test_service_status_required_fields(self):
"""Test that required fields are enforced"""
with pytest.raises(ValueError):
ServiceStatus() # Missing required fields
class TestHAAttributes:
"""Test HAAttributes model"""
def test_ha_attributes_creation(self):
"""Test creating HAAttributes instance"""
attrs = HAAttributes(
unit_of_measurement="°C",
friendly_name="Living Room Temperature"
)
assert attrs.unit_of_measurement == "°C"
assert attrs.friendly_name == "Living Room Temperature"
def test_ha_attributes_optional_fields(self):
"""Test that fields are optional"""
attrs = HAAttributes()
assert attrs.unit_of_measurement is None
assert attrs.friendly_name is None
class TestHAEntity:
"""Test HAEntity model"""
def test_ha_entity_creation(self):
"""Test creating HAEntity instance"""
attributes = HAAttributes(
unit_of_measurement="°C",
friendly_name="Temperature"
)
entity = HAEntity(
entity_id="sensor.temperature",
state="22.5",
attributes=attributes
)
assert entity.entity_id == "sensor.temperature"
assert entity.state == "22.5"
assert entity.attributes.unit_of_measurement == "°C"
class TestFrigateEvent:
"""Test FrigateEvent model"""
def test_frigate_event_creation(self):
"""Test creating FrigateEvent instance"""
event = FrigateEvent(
id="event_123",
timestamp="2024-01-01T12:00:00Z",
camera="front_door",
label="person",
confidence=0.95
)
assert event.id == "event_123"
assert event.camera == "front_door"
assert event.label == "person"
assert event.confidence == 0.95
def test_frigate_event_confidence_validation(self):
"""Test confidence validation (0-1 range)"""
# Valid confidence
event = FrigateEvent(
id="event_123",
timestamp="2024-01-01T12:00:00Z",
camera="front_door",
label="person",
confidence=0.5
)
assert event.confidence == 0.5
# Invalid confidence (too high)
with pytest.raises(ValueError):
FrigateEvent(
id="event_123",
timestamp="2024-01-01T12:00:00Z",
camera="front_door",
label="person",
confidence=1.5
)
# Invalid confidence (negative)
with pytest.raises(ValueError):
FrigateEvent(
id="event_123",
timestamp="2024-01-01T12:00:00Z",
camera="front_door",
label="person",
confidence=-0.1
)
class TestEventData:
"""Test EventData model"""
def test_event_data_creation(self):
"""Test creating EventData instance"""
event = EventData(
service="home_assistant",
event_type="state_changed",
metadata={"entity_id": "sensor.temperature", "new_state": "22.5"}
)
assert event.service == "home_assistant"
assert event.event_type == "state_changed"
assert event.metadata["entity_id"] == "sensor.temperature"
def test_event_data_default_metadata(self):
"""Test default metadata is empty dict"""
event = EventData(
service="test_service",
event_type="test_event"
)
assert event.metadata == {}
class TestHealthResponse:
"""Test HealthResponse model"""
def test_health_response_creation(self):
"""Test creating HealthResponse instance"""
timestamp = datetime.now().isoformat()
health = HealthResponse(
status="healthy",
timestamp=timestamp
)
assert health.status == "healthy"
assert health.timestamp == timestamp
class TestRootResponse:
"""Test RootResponse model"""
def test_root_response_creation(self):
"""Test creating RootResponse instance"""
response = RootResponse(
message="Test API",
version="1.0.0"
)
assert response.message == "Test API"
assert response.version == "1.0.0"