"""wizard.py DTO 单元测试。 覆盖 ``WizardFieldType`` 枚举与 ``WizardField`` / ``WizardStep`` / ``WizardConfigPatch`` / ``WizardStepResult`` 的字段赋值、默认值与 不可变语义。 """ from __future__ import annotations import dataclasses from typing import Any import pytest from yuxi.channels.contract.dtos.wizard import ( WizardConfigPatch, WizardField, WizardFieldType, WizardStep, WizardStepResult, ) pytestmark = pytest.mark.unit @pytest.mark.unit class TestWizardFieldType: """向导字段类型枚举测试。""" def test_enum_values_are_correct(self): # Arrange # Act # Assert assert WizardFieldType.TEXT == "text" assert WizardFieldType.PASSWORD == "password" assert WizardFieldType.SELECT == "select" assert WizardFieldType.OAUTH == "oauth" assert WizardFieldType.WEBHOOK_URL == "webhook_url" assert WizardFieldType.CHECKBOX == "checkbox" assert WizardFieldType.QR_LOGIN == "qr_login" def test_enum_inherits_from_str(self): # Arrange # Act # Assert assert issubclass(WizardFieldType, str) assert isinstance(WizardFieldType.TEXT, str) def test_enum_has_seven_members(self): # Arrange # Act members = list(WizardFieldType) # Assert assert len(members) == 7 @pytest.mark.unit class TestWizardField: """向导字段 DTO 测试。""" def test_required_fields_are_assigned(self): # Arrange # Act field = WizardField(name="app_id", field_type=WizardFieldType.TEXT) # Assert assert field.name == "app_id" assert field.field_type is WizardFieldType.TEXT def test_defaults_are_correct(self): # Arrange # Act field = WizardField(name="app_id", field_type=WizardFieldType.TEXT) # Assert assert field.label == "" assert field.required is True assert field.default is None assert field.placeholder == "" assert field.help == "" assert field.options == () assert field.sensitive is False def test_custom_values_are_assigned(self): # Arrange # Act field = WizardField( name="bot_token", field_type=WizardFieldType.PASSWORD, label="机器人 Token", required=True, default="", placeholder="请输入 Token", help="在飞书开放平台获取", options=("a", "b"), sensitive=True, ) # Assert assert field.label == "机器人 Token" assert field.default == "" assert field.placeholder == "请输入 Token" assert field.help == "在飞书开放平台获取" assert field.options == ("a", "b") assert field.sensitive is True def test_options_is_tuple(self): # Arrange # Act field = WizardField( name="env", field_type=WizardFieldType.SELECT, options=("dev", "prod"), ) # Assert assert isinstance(field.options, tuple) def test_is_frozen(self): # Arrange field = WizardField(name="app_id", field_type=WizardFieldType.TEXT) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): field.name = "other" # type: ignore[misc] @pytest.mark.unit class TestWizardStep: """向导步骤 DTO 测试。""" def test_required_fields_are_assigned(self): # Arrange fields = (WizardField(name="app_id", field_type=WizardFieldType.TEXT),) # Act step = WizardStep(step_id="basic", step_name="基础配置", fields=fields) # Assert assert step.step_id == "basic" assert step.step_name == "基础配置" assert step.fields is fields def test_defaults_are_correct(self): # Arrange # Act step = WizardStep(step_id="basic", step_name="基础配置", fields=()) # Assert assert step.description == "" assert step.skippable is False def test_fields_is_tuple(self): # Arrange # Act step = WizardStep( step_id="basic", step_name="基础配置", fields=(WizardField(name="a", field_type=WizardFieldType.TEXT),), ) # Assert assert isinstance(step.fields, tuple) def test_is_frozen(self): # Arrange step = WizardStep(step_id="basic", step_name="基础配置", fields=()) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): step.step_id = "other" # type: ignore[misc] @pytest.mark.unit class TestWizardConfigPatch: """向导配置补丁 DTO 测试。""" def test_fields_are_assigned(self): # Arrange values: dict[str, Any] = {"app_id": "xxx", "token": "yyy"} # Act patch = WizardConfigPatch(step_id="basic", values=values) # Assert assert patch.step_id == "basic" assert patch.values == values def test_is_frozen(self): # Arrange patch = WizardConfigPatch(step_id="basic", values={}) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): patch.step_id = "other" # type: ignore[misc] @pytest.mark.unit class TestWizardStepResult: """向导步骤结果 DTO 测试。""" def test_valid_result_defaults(self): # Arrange # Act result = WizardStepResult(step_id="basic", valid=True) # Assert assert result.errors == () assert result.config_patch is None def test_failed_result_with_errors(self): # Arrange # Act result = WizardStepResult( step_id="basic", valid=False, errors=("app_id is required", "token is required"), ) # Assert assert result.valid is False assert result.errors == ("app_id is required", "token is required") assert result.config_patch is None def test_valid_result_with_config_patch(self): # Arrange patch = WizardConfigPatch(step_id="basic", values={"app_id": "xxx"}) # Act result = WizardStepResult(step_id="basic", valid=True, config_patch=patch) # Assert assert result.config_patch is patch def test_errors_is_tuple(self): # Arrange # Act result = WizardStepResult(step_id="basic", valid=False, errors=("err1",)) # Assert assert isinstance(result.errors, tuple) def test_is_frozen(self): # Arrange result = WizardStepResult(step_id="basic", valid=True) # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): result.valid = False # type: ignore[misc]