Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ lint-mypy:
pipenv run mypy src/enapter_mcp_server

.PHONY: test
test: test-integration
test: test-unit test-integration

.PHONY: test-unit
test-unit:
pipenv run pytest -vv --capture=no tests/unit
Comment thread
rnovatorov marked this conversation as resolved.
Outdated

.PHONY: test-integration
test-integration:
Expand Down
Empty file added tests/unit/__init__.py
Empty file.
Empty file added tests/unit/mcp/__init__.py
Empty file.
Empty file.
100 changes: 100 additions & 0 deletions tests/unit/mcp/models/test_alert_declaration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from enapter_mcp_server.mcp.models.alert_declaration import AlertDeclaration
from enapter_mcp_server.mcp.models.alert_severity import AlertSeverity
Comment thread
rnovatorov marked this conversation as resolved.
Outdated


class TestAlertDeclaration:
"""Test cases for AlertDeclaration model."""

def test_alert_declaration_creation(self) -> None:
Comment thread
rnovatorov marked this conversation as resolved.
Outdated
"""Test creating AlertDeclaration instance."""
alert = AlertDeclaration(
name="high_temperature",
display_name="High Temperature",
severity=AlertSeverity.WARNING,
description="Temperature exceeded safe threshold",
troubleshooting=["Check cooling system", "Reduce load"],
components=["sensor", "cooling_unit"],
conditions=["temperature > 80"],
)

assert alert.name == "high_temperature"
assert alert.display_name == "High Temperature"
assert alert.severity == AlertSeverity.WARNING
assert alert.description == "Temperature exceeded safe threshold"
assert alert.troubleshooting == ["Check cooling system", "Reduce load"]
assert alert.components == ["sensor", "cooling_unit"]
assert alert.conditions == ["temperature > 80"]

def test_alert_declaration_with_none_values(self) -> None:
"""Test creating AlertDeclaration with None optional values."""
alert = AlertDeclaration(
name="simple_alert",
display_name="Simple Alert",
severity=AlertSeverity.INFO,
description=None,
troubleshooting=None,
components=None,
conditions=None,
)

assert alert.name == "simple_alert"
assert alert.display_name == "Simple Alert"
assert alert.severity == AlertSeverity.INFO
assert alert.description is None
assert alert.troubleshooting is None
assert alert.components is None
assert alert.conditions is None

def test_alert_declaration_from_dto(self) -> None:
"""Test creating AlertDeclaration from DTO."""
dto = {
"display_name": "Low Pressure Alert",
"severity": "error",
"description": "Pressure below minimum threshold",
"troubleshooting": ["Check pressure sensor", "Inspect valves"],
"components": ["pressure_sensor", "valve_control"],
"conditions": ["pressure < 10"],
}

alert = AlertDeclaration.from_dto("low_pressure", dto)

assert alert.name == "low_pressure"
assert alert.display_name == "Low Pressure Alert"
assert alert.severity == AlertSeverity.ERROR
assert alert.description == "Pressure below minimum threshold"
assert alert.troubleshooting == ["Check pressure sensor", "Inspect valves"]
assert alert.components == ["pressure_sensor", "valve_control"]
assert alert.conditions == ["pressure < 10"]

def test_alert_declaration_from_dto_minimal(self) -> None:
"""Test creating AlertDeclaration from minimal DTO."""
dto = {
"display_name": "Basic Alert",
"severity": "info",
}

alert = AlertDeclaration.from_dto("basic_alert", dto)

assert alert.name == "basic_alert"
assert alert.display_name == "Basic Alert"
assert alert.severity == AlertSeverity.INFO
assert alert.description is None
assert alert.troubleshooting is None
assert alert.components is None
assert alert.conditions is None

def test_alert_declaration_all_severities(self) -> None:
"""Test AlertDeclaration with all severity levels."""
severities = [AlertSeverity.INFO, AlertSeverity.WARNING, AlertSeverity.ERROR]

for severity in severities:
alert = AlertDeclaration(
name=f"alert_{severity.value}",
display_name=f"Alert {severity.value}",
severity=severity,
description=None,
troubleshooting=None,
components=None,
conditions=None,
)
assert alert.severity == severity
31 changes: 31 additions & 0 deletions tests/unit/mcp/models/test_alert_severity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pytest

from enapter_mcp_server.mcp.models.alert_severity import AlertSeverity


class TestAlertSeverity:
Comment thread
rnovatorov marked this conversation as resolved.
Outdated
"""Test cases for AlertSeverity enum."""

def test_alert_severity_values(self) -> None:
"""Test that AlertSeverity has expected values."""
assert AlertSeverity.INFO == "info"
assert AlertSeverity.WARNING == "warning"
assert AlertSeverity.ERROR == "error"

def test_alert_severity_from_string(self) -> None:
"""Test creating AlertSeverity from string."""
assert AlertSeverity("info") == AlertSeverity.INFO
assert AlertSeverity("warning") == AlertSeverity.WARNING
assert AlertSeverity("error") == AlertSeverity.ERROR

def test_alert_severity_invalid_value(self) -> None:
"""Test that invalid value raises ValueError."""
with pytest.raises(ValueError):
AlertSeverity("invalid")

def test_alert_severity_membership(self) -> None:
"""Test that all expected values are members of the enum."""
assert "info" in [s.value for s in AlertSeverity]
assert "warning" in [s.value for s in AlertSeverity]
assert "error" in [s.value for s in AlertSeverity]
assert len(list(AlertSeverity)) == 3
30 changes: 30 additions & 0 deletions tests/unit/mcp/models/test_blueprint_section.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest

from enapter_mcp_server.mcp.models.blueprint_section import BlueprintSection


class TestBlueprintSection:
"""Test cases for BlueprintSection enum."""

def test_blueprint_section_values(self) -> None:
"""Test that BlueprintSection has expected values."""
assert BlueprintSection.TELEMETRY == "telemetry"
assert BlueprintSection.PROPERTIES == "properties"
assert BlueprintSection.ALERTS == "alerts"

def test_blueprint_section_from_string(self) -> None:
"""Test creating BlueprintSection from string."""
assert BlueprintSection("telemetry") == BlueprintSection.TELEMETRY
assert BlueprintSection("properties") == BlueprintSection.PROPERTIES
assert BlueprintSection("alerts") == BlueprintSection.ALERTS

def test_blueprint_section_invalid_value(self) -> None:
"""Test that invalid value raises ValueError."""
with pytest.raises(ValueError):
BlueprintSection("invalid_section")

def test_blueprint_section_membership(self) -> None:
"""Test that all expected values are members of the enum."""
expected_sections = ["telemetry", "properties", "alerts"]
actual_values = [s.value for s in BlueprintSection]
assert sorted(actual_values) == sorted(expected_sections)
98 changes: 98 additions & 0 deletions tests/unit/mcp/models/test_blueprint_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from typing import Any

from enapter_mcp_server.mcp.models.blueprint_summary import BlueprintSummary


class TestBlueprintSummary:
"""Test cases for BlueprintSummary model."""

def test_blueprint_summary_creation(self) -> None:
"""Test creating BlueprintSummary instance."""
summary = BlueprintSummary(
description="Test device",
vendor="Enapter",
properties_total=5,
telemetry_attributes_total=10,
alerts_total=3,
)

assert summary.description == "Test device"
assert summary.vendor == "Enapter"
assert summary.properties_total == 5
assert summary.telemetry_attributes_total == 10
assert summary.alerts_total == 3

def test_blueprint_summary_with_none_values(self) -> None:
"""Test creating BlueprintSummary with None values."""
summary = BlueprintSummary(
description=None,
vendor=None,
properties_total=0,
telemetry_attributes_total=0,
alerts_total=0,
)

assert summary.description is None
assert summary.vendor is None
assert summary.properties_total == 0
assert summary.telemetry_attributes_total == 0
assert summary.alerts_total == 0

def test_blueprint_summary_from_manifest(self) -> None:
"""Test creating BlueprintSummary from manifest."""
manifest = {
"description": "Electrolyzer device",
"vendor": "Enapter",
"properties": {
"firmware_version": {"type": "string"},
"serial_number": {"type": "string"},
"model": {"type": "string"},
},
"telemetry": {
"temperature": {"type": "float"},
"pressure": {"type": "float"},
"voltage": {"type": "float"},
"current": {"type": "float"},
},
"alerts": {
"high_temperature": {"severity": "warning"},
"low_pressure": {"severity": "error"},
},
}

summary = BlueprintSummary.from_manifest(manifest)

assert summary.description == "Electrolyzer device"
assert summary.vendor == "Enapter"
assert summary.properties_total == 3
assert summary.telemetry_attributes_total == 4
assert summary.alerts_total == 2

def test_blueprint_summary_from_manifest_empty(self) -> None:
"""Test creating BlueprintSummary from empty manifest."""
manifest: dict[str, Any] = {}

summary = BlueprintSummary.from_manifest(manifest)

assert summary.description is None
assert summary.vendor is None
assert summary.properties_total == 0
assert summary.telemetry_attributes_total == 0
assert summary.alerts_total == 0

def test_blueprint_summary_from_manifest_partial(self) -> None:
"""Test creating BlueprintSummary from partial manifest."""
manifest = {
"description": "Partial device",
"telemetry": {
"temp": {"type": "float"},
},
}

summary = BlueprintSummary.from_manifest(manifest)

assert summary.description == "Partial device"
assert summary.vendor is None
assert summary.properties_total == 0
assert summary.telemetry_attributes_total == 1
assert summary.alerts_total == 0
30 changes: 30 additions & 0 deletions tests/unit/mcp/models/test_connectivity_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest

from enapter_mcp_server.mcp.models.connectivity_status import ConnectivityStatus


class TestConnectivityStatus:
"""Test cases for ConnectivityStatus enum."""

def test_connectivity_status_values(self) -> None:
"""Test that ConnectivityStatus has expected values."""
assert ConnectivityStatus.UNKNOWN == "UNKNOWN"
assert ConnectivityStatus.ONLINE == "ONLINE"
assert ConnectivityStatus.OFFLINE == "OFFLINE"

def test_connectivity_status_from_string(self) -> None:
"""Test creating ConnectivityStatus from string."""
assert ConnectivityStatus("UNKNOWN") == ConnectivityStatus.UNKNOWN
assert ConnectivityStatus("ONLINE") == ConnectivityStatus.ONLINE
assert ConnectivityStatus("OFFLINE") == ConnectivityStatus.OFFLINE

def test_connectivity_status_invalid_value(self) -> None:
"""Test that invalid value raises ValueError."""
with pytest.raises(ValueError):
ConnectivityStatus("INVALID")

def test_connectivity_status_membership(self) -> None:
"""Test that all expected values are members of the enum."""
expected_statuses = ["UNKNOWN", "ONLINE", "OFFLINE"]
actual_values = [s.value for s in ConnectivityStatus]
assert sorted(actual_values) == sorted(expected_statuses)
91 changes: 91 additions & 0 deletions tests/unit/mcp/models/test_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from unittest.mock import Mock
Comment thread
rnovatorov marked this conversation as resolved.
Outdated

from enapter_mcp_server.mcp.models.device import Device
from enapter_mcp_server.mcp.models.device_type import DeviceType


class TestDevice:
"""Test cases for Device model."""

def test_device_creation(self) -> None:
"""Test creating Device instance."""
device = Device(
id="device-123",
name="Test Device",
site_id="site-456",
type=DeviceType.LUA,
)

assert device.id == "device-123"
assert device.name == "Test Device"
assert device.site_id == "site-456"
assert device.type == DeviceType.LUA

def test_device_validation(self) -> None:
Comment thread
rnovatorov marked this conversation as resolved.
Outdated
"""Test pydantic validation for Device."""
# Valid device
device = Device(
id="uuid-123",
name="My Device",
site_id="site-uuid",
type=DeviceType.GATEWAY,
)
assert isinstance(device.id, str)
assert isinstance(device.name, str)
assert isinstance(device.site_id, str)
assert isinstance(device.type, DeviceType)

def test_device_from_domain(self) -> None:
"""Test creating Device from domain object."""
# Mock domain device object
domain_device = Mock()
domain_device.id = "device-789"
domain_device.name = "Production Device"
domain_device.site_id = "site-999"
domain_device.type = Mock()
domain_device.type.value = "NATIVE"

device = Device.from_domain(domain_device)

assert device.id == "device-789"
assert device.name == "Production Device"
assert device.site_id == "site-999"
assert device.type == DeviceType.NATIVE

def test_device_with_all_device_types(self) -> None:
Comment thread
rnovatorov marked this conversation as resolved.
Outdated
"""Test Device creation with all DeviceType values."""
device_types = [
DeviceType.LUA,
DeviceType.VIRTUAL_UCM,
DeviceType.HARDWARE_UCM,
DeviceType.STANDALONE,
DeviceType.GATEWAY,
DeviceType.LINK_MASTER_UCM,
DeviceType.LINK_SLAVE_UCM,
DeviceType.EMBEDDED_UCM,
DeviceType.NATIVE,
]

for device_type in device_types:
device = Device(
id=f"device-{device_type.value}",
name=f"Device {device_type.value}",
site_id="site-123",
type=device_type,
)
assert device.type == device_type

def test_device_equality(self) -> None:
Comment thread
rnovatorov marked this conversation as resolved.
Outdated
"""Test Device equality comparison."""
device1 = Device(
id="device-1", name="Device A", site_id="site-1", type=DeviceType.LUA
)
device2 = Device(
id="device-1", name="Device A", site_id="site-1", type=DeviceType.LUA
)
device3 = Device(
id="device-2", name="Device B", site_id="site-1", type=DeviceType.GATEWAY
)

assert device1 == device2
assert device1 != device3
Loading