Files
noteflow/tests/infrastructure/test_integration_converters.py
Travis Vasceannie b11633192a
Some checks failed
CI / test-python (push) Failing after 22m14s
CI / test-rust (push) Has been cancelled
CI / test-typescript (push) Has been cancelled
deps
2026-01-24 21:31:58 +00:00

459 lines
19 KiB
Python

"""Tests for IntegrationConverter and SyncRunConverter infrastructure conversions."""
from __future__ import annotations
from collections.abc import Mapping
from datetime import UTC, datetime
from unittest.mock import MagicMock
from uuid import uuid4
import pytest
from noteflow.domain.entities.integration import (
Integration,
IntegrationStatus,
IntegrationType,
SyncErrorCode,
SyncRun,
SyncRunStatus,
)
from noteflow.infrastructure.converters.integration_converters import (
IntegrationConverter,
SyncRunConverter,
)
def _create_mock_integration_orm_from_kwargs(orm_kwargs: Mapping[str, object]) -> MagicMock:
"""Create a mock ORM model from Integration kwargs dictionary."""
mock_orm = MagicMock()
mock_orm.id = orm_kwargs["id"]
mock_orm.workspace_id = orm_kwargs["workspace_id"]
mock_orm.name = orm_kwargs["name"]
mock_orm.type = orm_kwargs["type"]
mock_orm.status = orm_kwargs["status"]
mock_orm.config = orm_kwargs["config"]
mock_orm.last_sync = orm_kwargs["last_sync"]
mock_orm.error_message = orm_kwargs["error_message"]
mock_orm.created_at = orm_kwargs["created_at"]
mock_orm.updated_at = orm_kwargs["updated_at"]
return mock_orm
def _create_mock_sync_run_orm_from_kwargs(orm_kwargs: Mapping[str, object]) -> MagicMock:
"""Create a mock ORM model from SyncRun kwargs dictionary."""
mock_orm = MagicMock()
mock_orm.id = orm_kwargs["id"]
mock_orm.integration_id = orm_kwargs["integration_id"]
mock_orm.status = orm_kwargs["status"]
mock_orm.started_at = orm_kwargs["started_at"]
mock_orm.ended_at = orm_kwargs["ended_at"]
mock_orm.duration_ms = orm_kwargs["duration_ms"]
mock_orm.error_code = orm_kwargs["error_code"]
mock_orm.stats = orm_kwargs["stats"]
return mock_orm
# Test constants for sync run metrics
SYNC_RUN_ITEMS_SYNCED = 15
"""Number of items synced in a standard test sync run fixture."""
SYNC_RUN_DURATION_MS_SHORT = 5000
"""Short sync run duration in milliseconds (5 seconds)."""
SYNC_RUN_DURATION_MS_MEDIUM = 10000
"""Medium sync run duration in milliseconds (10 seconds)."""
SYNC_RUN_ITEMS_COMPLETE = 25
"""Number of items in a complete sync run test case."""
class TestIntegrationConverterOrmToDomain:
"""Tests for IntegrationConverter.orm_to_domain."""
@pytest.fixture
def mock_integration_model(self) -> MagicMock:
"""Create mock ORM IntegrationModel with typical values."""
model = MagicMock()
model.id = uuid4()
model.workspace_id = uuid4()
model.name = "Google Calendar"
model.type = "calendar"
model.status = "connected"
model.config = {"provider_email": "user@example.com", "refresh_token": "xyz"}
model.last_sync = datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC)
model.error_message = None
model.created_at = datetime(2024, 1, 10, 10, 0, 0, tzinfo=UTC)
model.updated_at = datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC)
return model
def test_integration_orm_to_domain(self, mock_integration_model: MagicMock) -> None:
"""Convert ORM model to domain Integration."""
result = IntegrationConverter.orm_to_domain(mock_integration_model)
assert isinstance(result, Integration), "Should return Integration instance"
assert result.id == mock_integration_model.id, "ID should match"
assert result.workspace_id == mock_integration_model.workspace_id, (
"Workspace ID should match"
)
assert result.name == "Google Calendar", "Name should match"
assert result.type == IntegrationType.CALENDAR, "Type should be enum"
assert result.status == IntegrationStatus.CONNECTED, "Status should be enum"
assert result.last_sync == mock_integration_model.last_sync, "Last sync should match"
assert result.error_message is None, "Error message should be None"
def test_converts_type_string_to_enum(self, mock_integration_model: MagicMock) -> None:
"""Type string is converted to IntegrationType enum."""
mock_integration_model.type = "email"
result = IntegrationConverter.orm_to_domain(mock_integration_model)
assert result.type == IntegrationType.EMAIL, "Type should be EMAIL enum"
assert isinstance(result.type, IntegrationType), "Type should be IntegrationType instance"
@pytest.mark.parametrize(
("type_string", "expected_enum"),
[
("auth", IntegrationType.AUTH),
("email", IntegrationType.EMAIL),
("calendar", IntegrationType.CALENDAR),
("pkm", IntegrationType.PKM),
("custom", IntegrationType.CUSTOM),
],
)
def test_all_type_strings_convert_correctly(
self,
mock_integration_model: MagicMock,
type_string: str,
expected_enum: IntegrationType,
) -> None:
"""All IntegrationType strings convert to correct enum values."""
mock_integration_model.type = type_string
result = IntegrationConverter.orm_to_domain(mock_integration_model)
assert result.type == expected_enum, (
f"Type string '{type_string}' should convert to {expected_enum}"
)
@pytest.mark.parametrize(
("status_string", "expected_enum"),
[
("disconnected", IntegrationStatus.DISCONNECTED),
("connected", IntegrationStatus.CONNECTED),
("error", IntegrationStatus.ERROR),
],
)
def test_integration_status_strings_convert(
self,
mock_integration_model: MagicMock,
status_string: str,
expected_enum: IntegrationStatus,
) -> None:
"""All IntegrationStatus strings convert to correct enum values."""
mock_integration_model.status = status_string
result = IntegrationConverter.orm_to_domain(mock_integration_model)
assert result.status == expected_enum, (
f"Status string '{status_string}' should convert to {expected_enum}"
)
def test_converts_config_dict(self, mock_integration_model: MagicMock) -> None:
"""Config is converted to dict from ORM model."""
result = IntegrationConverter.orm_to_domain(mock_integration_model)
assert isinstance(result.config, dict), "Config should be dict"
assert result.config["provider_email"] == "user@example.com", (
"Provider email should be preserved"
)
def test_handles_none_config(self, mock_integration_model: MagicMock) -> None:
"""None config in ORM becomes empty dict in domain."""
mock_integration_model.config = None
result = IntegrationConverter.orm_to_domain(mock_integration_model)
assert result.config == {}, "None config should become empty dict"
def test_integration_handles_error_status(self, mock_integration_model: MagicMock) -> None:
"""Error status with error message is converted correctly."""
mock_integration_model.status = "error"
mock_integration_model.error_message = "Token expired"
result = IntegrationConverter.orm_to_domain(mock_integration_model)
assert result.status == IntegrationStatus.ERROR, "Status should be ERROR enum"
assert result.error_message == "Token expired", "Error message should be preserved"
class TestIntegrationConverterToOrmKwargs:
"""Tests for IntegrationConverter.to_integration_orm_kwargs."""
@pytest.fixture
def integration_entity(self) -> Integration:
"""Create domain Integration entity for testing."""
return Integration(
id=uuid4(),
workspace_id=uuid4(),
name="Outlook Calendar",
type=IntegrationType.CALENDAR,
status=IntegrationStatus.CONNECTED,
config={"provider_email": "test@outlook.com"},
last_sync=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
error_message=None,
created_at=datetime(2024, 1, 10, 10, 0, 0, tzinfo=UTC),
updated_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
)
def test_integration_to_orm_kwargs(self, integration_entity: Integration) -> None:
"""Convert domain Integration to ORM kwargs dict."""
result = IntegrationConverter.to_integration_orm_kwargs(integration_entity)
assert result["id"] == integration_entity.id, "ID should be preserved"
assert result["workspace_id"] == integration_entity.workspace_id, (
"Workspace ID should be preserved"
)
assert result["name"] == "Outlook Calendar", "Name should be preserved"
assert result["type"] == "calendar", "Type should be string value"
assert result["status"] == "connected", "Status should be string value"
assert result["config"] == {"provider_email": "test@outlook.com"}, (
"Config should be preserved"
)
assert result["error_message"] is None, "Error message should be None"
@pytest.mark.parametrize(
("type_enum", "expected_string"),
[
(IntegrationType.AUTH, "auth"),
(IntegrationType.EMAIL, "email"),
(IntegrationType.CALENDAR, "calendar"),
(IntegrationType.PKM, "pkm"),
(IntegrationType.CUSTOM, "custom"),
],
)
def test_all_type_enums_convert_to_strings(
self, type_enum: IntegrationType, expected_string: str
) -> None:
"""All IntegrationType enums convert to correct string values."""
integration = Integration(
id=uuid4(),
workspace_id=uuid4(),
name="Test",
type=type_enum,
status=IntegrationStatus.DISCONNECTED,
)
result = IntegrationConverter.to_integration_orm_kwargs(integration)
assert result["type"] == expected_string, (
f"Type enum {type_enum} should convert to '{expected_string}'"
)
@pytest.mark.parametrize(
("status_enum", "expected_string"),
[
(IntegrationStatus.DISCONNECTED, "disconnected"),
(IntegrationStatus.CONNECTED, "connected"),
(IntegrationStatus.ERROR, "error"),
],
)
def test_integration_status_enums_to_strings(
self, status_enum: IntegrationStatus, expected_string: str
) -> None:
"""All IntegrationStatus enums convert to correct string values."""
integration = Integration(
id=uuid4(),
workspace_id=uuid4(),
name="Test",
type=IntegrationType.CALENDAR,
status=status_enum,
)
result = IntegrationConverter.to_integration_orm_kwargs(integration)
assert result["status"] == expected_string, (
f"Status enum {status_enum} should convert to '{expected_string}'"
)
class TestSyncRunConverterOrmToDomain:
"""Tests for SyncRunConverter.orm_to_domain."""
@pytest.fixture
def mock_sync_run_model(self) -> MagicMock:
"""Create mock ORM IntegrationSyncRunModel with typical values."""
model = MagicMock()
model.id = uuid4()
model.integration_id = uuid4()
model.status = "success"
model.started_at = datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC)
model.ended_at = datetime(2024, 1, 15, 12, 0, 5, tzinfo=UTC)
model.duration_ms = SYNC_RUN_DURATION_MS_SHORT
model.error_code = None
model.stats = {"items_synced": 10, "items_total": SYNC_RUN_ITEMS_SYNCED}
return model
def test_sync_run_orm_to_domain(self, mock_sync_run_model: MagicMock) -> None:
"""Convert ORM model to domain SyncRun."""
result = SyncRunConverter.orm_to_domain(mock_sync_run_model)
assert isinstance(result, SyncRun), "Should return SyncRun instance"
assert result.id == mock_sync_run_model.id, "ID should match"
assert result.integration_id == mock_sync_run_model.integration_id, (
"Integration ID should match"
)
assert result.status == SyncRunStatus.SUCCESS, "Status should be enum"
assert result.duration_ms == SYNC_RUN_DURATION_MS_SHORT, "Duration should match"
assert result.error_code is None, "Error code should be None"
@pytest.mark.parametrize(
("status_string", "expected_enum"),
[
("running", SyncRunStatus.RUNNING),
("success", SyncRunStatus.SUCCESS),
("error", SyncRunStatus.ERROR),
],
)
def test_sync_run_status_strings_convert(
self,
mock_sync_run_model: MagicMock,
status_string: str,
expected_enum: SyncRunStatus,
) -> None:
"""All SyncRunStatus strings convert to correct enum values."""
mock_sync_run_model.status = status_string
result = SyncRunConverter.orm_to_domain(mock_sync_run_model)
assert result.status == expected_enum, (
f"Status string '{status_string}' should convert to {expected_enum}"
)
def test_converts_stats_dict(self, mock_sync_run_model: MagicMock) -> None:
"""Stats is converted to dict from ORM model."""
result = SyncRunConverter.orm_to_domain(mock_sync_run_model)
assert isinstance(result.stats, dict), "Stats should be dict"
assert result.stats["items_synced"] == 10, "Items synced count should be preserved"
assert result.stats["items_total"] == SYNC_RUN_ITEMS_SYNCED, (
"Items total count should be preserved"
)
def test_handles_none_stats(self, mock_sync_run_model: MagicMock) -> None:
"""None stats in ORM becomes empty dict in domain."""
mock_sync_run_model.stats = None
result = SyncRunConverter.orm_to_domain(mock_sync_run_model)
assert result.stats == {}, "None stats should become empty dict"
def test_sync_run_handles_error_status(self, mock_sync_run_model: MagicMock) -> None:
"""Error status with error code is converted correctly."""
mock_sync_run_model.status = "error"
mock_sync_run_model.error_code = "provider_error"
result = SyncRunConverter.orm_to_domain(mock_sync_run_model)
assert result.status == SyncRunStatus.ERROR, "Status should be ERROR enum"
assert result.error_code == SyncErrorCode.PROVIDER_ERROR, "Error code should be preserved"
class TestSyncRunConverterToOrmKwargs:
"""Tests for SyncRunConverter.to_sync_run_orm_kwargs."""
def test_sync_run_to_orm_kwargs(self) -> None:
"""Convert domain SyncRun to ORM kwargs dict."""
sync_run = SyncRun(
id=uuid4(),
integration_id=uuid4(),
status=SyncRunStatus.SUCCESS,
started_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
ended_at=datetime(2024, 1, 15, 12, 0, 10, tzinfo=UTC),
duration_ms=SYNC_RUN_DURATION_MS_MEDIUM,
error_code=None,
stats={"items_synced": SYNC_RUN_ITEMS_COMPLETE},
)
result = SyncRunConverter.to_sync_run_orm_kwargs(sync_run)
assert result["id"] == sync_run.id, "ID should be preserved"
assert result["integration_id"] == sync_run.integration_id, (
"Integration ID should be preserved"
)
assert result["status"] == "success", "Status should be string value"
assert result["duration_ms"] == SYNC_RUN_DURATION_MS_MEDIUM, "Duration should be preserved"
assert result["stats"] == {"items_synced": SYNC_RUN_ITEMS_COMPLETE}, (
"Stats should be preserved"
)
@pytest.mark.parametrize(
("status_enum", "expected_string"),
[
(SyncRunStatus.RUNNING, "running"),
(SyncRunStatus.SUCCESS, "success"),
(SyncRunStatus.ERROR, "error"),
],
)
def test_sync_run_status_enums_to_strings(
self, status_enum: SyncRunStatus, expected_string: str
) -> None:
"""All SyncRunStatus enums convert to correct string values."""
sync_run = SyncRun(
id=uuid4(),
integration_id=uuid4(),
status=status_enum,
started_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
)
result = SyncRunConverter.to_sync_run_orm_kwargs(sync_run)
assert result["status"] == expected_string, (
f"Status enum {status_enum} should convert to '{expected_string}'"
)
class TestIntegrationConverterRoundTrip:
"""Tests for round-trip conversion fidelity."""
@pytest.fixture
def round_trip_integration(self) -> Integration:
"""Create Integration entity for round-trip testing."""
return Integration(
id=uuid4(),
workspace_id=uuid4(),
name="Round Trip Integration",
type=IntegrationType.CALENDAR,
status=IntegrationStatus.CONNECTED,
config={"provider_email": "test@example.com", "scope": "calendar.read"},
last_sync=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
error_message=None,
created_at=datetime(2024, 1, 10, 10, 0, 0, tzinfo=UTC),
updated_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
)
@pytest.fixture
def round_trip_sync_run(self) -> SyncRun:
"""Create SyncRun entity for round-trip testing."""
return SyncRun(
id=uuid4(),
integration_id=uuid4(),
status=SyncRunStatus.SUCCESS,
started_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
ended_at=datetime(2024, 1, 15, 12, 0, 15, tzinfo=UTC),
duration_ms=15000,
error_code=None,
stats={"items_synced": 50, "items_total": 50},
)
def test_integration_domain_to_orm_to_domain_preserves_values(
self, round_trip_integration: Integration
) -> None:
"""Round-trip conversion preserves all Integration field values."""
orm_kwargs = IntegrationConverter.to_integration_orm_kwargs(round_trip_integration)
mock_orm = _create_mock_integration_orm_from_kwargs(orm_kwargs)
result = IntegrationConverter.orm_to_domain(mock_orm)
assert result.id == round_trip_integration.id, "ID preserved through round-trip"
assert result.workspace_id == round_trip_integration.workspace_id, "Workspace ID preserved"
assert result.name == round_trip_integration.name, "Name preserved"
assert result.type == round_trip_integration.type, "Type preserved"
assert result.status == round_trip_integration.status, "Status preserved"
assert result.config == round_trip_integration.config, "Config preserved"
assert result.last_sync == round_trip_integration.last_sync, "Last sync preserved"
def test_sync_run_domain_to_orm_to_domain_preserves_values(
self, round_trip_sync_run: SyncRun
) -> None:
"""Round-trip conversion preserves all SyncRun field values."""
orm_kwargs = SyncRunConverter.to_sync_run_orm_kwargs(round_trip_sync_run)
mock_orm = _create_mock_sync_run_orm_from_kwargs(orm_kwargs)
result = SyncRunConverter.orm_to_domain(mock_orm)
assert result.id == round_trip_sync_run.id, "ID preserved"
assert result.integration_id == round_trip_sync_run.integration_id, (
"Integration ID preserved"
)
assert result.status == round_trip_sync_run.status, "Status preserved"
assert result.duration_ms == round_trip_sync_run.duration_ms, "Duration preserved"
assert result.stats == round_trip_sync_run.stats, "Stats preserved"