Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 --cov --cov-report term-missing tests/unit

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

import enapter

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."""
# Create domain device object
domain_device = enapter.http.api.devices.Device(
id="device-789",
blueprint_id="blueprint-123",
name="Production Device",
site_id="site-999",
updated_at=datetime.datetime(2024, 1, 1, 12, 0, 0),
slug="production-device",
type=enapter.http.api.devices.DeviceType.NATIVE,
authorized_role=enapter.http.api.devices.AuthorizedRole.USER,
)

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
23 changes: 23 additions & 0 deletions tests/unit/mcp/models/test_site.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import enapter

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."""
# Create domain site object
domain_site = enapter.http.api.sites.Site(
id="site-456",
name="Production Site",
timezone="Asia/Tokyo",
version="V3",
)

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