Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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.
43 changes: 43 additions & 0 deletions tests/unit/mcp/models/test_alert_declaration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from enapter_mcp_server.mcp import models


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

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 = models.AlertDeclaration.from_dto("low_pressure", dto)

assert alert.name == "low_pressure"
assert alert.display_name == "Low Pressure Alert"
assert alert.severity == models.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 = models.AlertDeclaration.from_dto("basic_alert", dto)

assert alert.name == "basic_alert"
assert alert.display_name == "Basic Alert"
assert alert.severity == models.AlertSeverity.INFO
assert alert.description is None
assert alert.troubleshooting is None
assert alert.components is None
assert alert.conditions is None
66 changes: 66 additions & 0 deletions tests/unit/mcp/models/test_blueprint_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import Any

from enapter_mcp_server.mcp import models


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

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 = models.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 = models.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 = models.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
24 changes: 24 additions & 0 deletions tests/unit/mcp/models/test_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from unittest import mock

from enapter_mcp_server.mcp import models


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

def test_device_from_domain(self) -> None:
"""Test creating Device from domain object."""
# Mock domain device object
domain_device = mock.Mock()
Comment thread
rnovatorov marked this conversation as resolved.
Outdated
domain_device.id = "device-789"
domain_device.name = "Production Device"
domain_device.site_id = "site-999"
domain_device.type = mock.Mock()
domain_device.type.value = "NATIVE"

device = models.Device.from_domain(domain_device)

assert device.id == "device-789"
assert device.name == "Production Device"
assert device.site_id == "site-999"
assert device.type == models.DeviceType.NATIVE
57 changes: 57 additions & 0 deletions tests/unit/mcp/models/test_property_declaration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from enapter_mcp_server.mcp import models


class TestPropertyDeclaration:
"""Test cases for PropertyDeclaration model."""

def test_property_declaration_from_dto(self) -> None:
"""Test creating PropertyDeclaration from DTO."""
dto = {
"display_name": "Serial Number",
"type": "string",
"description": "Device serial number",
}

prop = models.PropertyDeclaration.from_dto("serial_number", dto)

assert prop.name == "serial_number"
assert prop.display_name == "Serial Number"
assert prop.data_type == models.PropertyDataType.STRING
assert prop.description == "Device serial number"
assert prop.enum is None
assert prop.unit is None

def test_property_declaration_from_dto_with_enum_and_unit(self) -> None:
"""Test creating PropertyDeclaration from DTO with enum and unit."""
dto = {
"display_name": "Status",
"type": "string",
"description": "Device status",
"enum": ["active", "inactive", "error"],
"unit": "state",
}

prop = models.PropertyDeclaration.from_dto("status", dto)

assert prop.name == "status"
assert prop.display_name == "Status"
assert prop.data_type == models.PropertyDataType.STRING
assert prop.description == "Device status"
assert prop.enum == ["active", "inactive", "error"]
assert prop.unit == "state"

def test_property_declaration_from_dto_minimal(self) -> None:
"""Test creating PropertyDeclaration from minimal DTO."""
dto = {
"display_name": "Simple Property",
"type": "integer",
}

prop = models.PropertyDeclaration.from_dto("simple", dto)

assert prop.name == "simple"
assert prop.display_name == "Simple Property"
assert prop.data_type == models.PropertyDataType.INTEGER
assert prop.description is None
assert prop.enum is None
assert prop.unit is None
21 changes: 21 additions & 0 deletions tests/unit/mcp/models/test_site.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from unittest import mock

from enapter_mcp_server.mcp import models


class TestSite:
"""Test cases for Site model."""

def test_site_from_domain(self) -> None:
"""Test creating Site from domain object."""
# Mock domain site object
domain_site = mock.Mock()
domain_site.id = "site-456"
domain_site.name = "Production Site"
domain_site.timezone = "Asia/Tokyo"

site = models.Site.from_domain(domain_site)

assert site.id == "site-456"
assert site.name == "Production Site"
assert site.timezone == "Asia/Tokyo"
56 changes: 56 additions & 0 deletions tests/unit/mcp/models/test_telemetry_attribute_declaration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from enapter_mcp_server.mcp import models


class TestTelemetryAttributeDeclaration:
"""Test cases for TelemetryAttributeDeclaration model."""

def test_telemetry_attribute_declaration_from_dto(self) -> None:
"""Test creating TelemetryAttributeDeclaration from DTO."""
dto = {
"display_name": "Voltage",
"type": "float",
"description": "Measured voltage",
"unit": "V",
}

attr = models.TelemetryAttributeDeclaration.from_dto("voltage", dto)

assert attr.name == "voltage"
assert attr.display_name == "Voltage"
assert attr.data_type == models.TelemetryAttributeDataType.FLOAT
assert attr.description == "Measured voltage"
assert attr.unit == "V"

def test_telemetry_attribute_declaration_from_dto_with_enum(self) -> None:
"""Test creating TelemetryAttributeDeclaration from DTO with enum."""
dto = {
"display_name": "Operation Mode",
"type": "string",
"description": "Current operation mode",
"enum": ["auto", "manual", "off"],
}

attr = models.TelemetryAttributeDeclaration.from_dto("mode", dto)

assert attr.name == "mode"
assert attr.display_name == "Operation Mode"
assert attr.data_type == models.TelemetryAttributeDataType.STRING
assert attr.description == "Current operation mode"
assert attr.enum == ["auto", "manual", "off"]
assert attr.unit is None

def test_telemetry_attribute_declaration_from_dto_minimal(self) -> None:
"""Test creating TelemetryAttributeDeclaration from minimal DTO."""
dto = {
"display_name": "Simple Attribute",
"type": "boolean",
}

attr = models.TelemetryAttributeDeclaration.from_dto("simple", dto)

assert attr.name == "simple"
assert attr.display_name == "Simple Attribute"
assert attr.data_type == models.TelemetryAttributeDataType.BOOLEAN
assert attr.description is None
assert attr.enum is None
assert attr.unit is None