ForcePilot/backend/test/unit/external_systems/integrations/test_schemas.py
Kris 15c1cd95ba test: 新增并完善外部系统单元测试覆盖
- 新增多个集成服务(Slack、Notion、Twilio、Microsoft365等)的元数据、常量、凭证、错误处理相关单元测试
- 新增持久化适配器、工作单元、健康检查仓储的单元测试
- 修复认证插件测试中缺失的凭证类型注册
- 修正密钥轮换调度器的异常类型匹配
- 完善审计日志测试用例,新增创建者/更新者字段校验
- 新增告警触发时持久化通知渠道的测试
- 精简核心模型测试代码,移除冗余的超时测试用例
2026-07-11 05:42:04 +08:00

214 lines
6.8 KiB
Python

"""integrations/schemas.py 数据结构单元测试。
覆盖 IntegrationMetadata 与 GeneratedToolsDraft 两个 Pydantic BaseModel
的字段声明、默认值、约束校验与 extra="forbid" 行为。
"""
from __future__ import annotations
from typing import Any
import pytest
from pydantic import ValidationError
from yuxi.external_systems.integrations.schemas import (
GeneratedToolsDraft,
IntegrationMetadata,
)
# ─── 辅助构造 ─────────────────────────────────────────────────────────────
def _metadata_kwargs(**overrides: Any) -> dict[str, Any]:
"""构造 IntegrationMetadata 最小合法字段,按需覆盖。"""
base: dict[str, Any] = {
"key": "salesforce",
"display_name": "Salesforce",
"adapter_type": "http",
"source_types": ["salesforce"],
}
base.update(overrides)
return base
# ─── IntegrationMetadata 字段声明 ─────────────────────────────────────────
@pytest.mark.unit
def test_integrationMetadata_fields_matchExpected():
# Arrange / Act
meta = IntegrationMetadata(
key="hubspot",
display_name="HubSpot",
description="CRM integration",
adapter_type="http",
source_types=["hubspot"],
supported_auth_types=["oauth2"],
tags=["crm", "marketing"],
icon="hubspot",
docs_url="https://docs.hubspot.com",
connection_extra_schema={"type": "object"},
example_config={"host": "api.hubspot.com"},
dependency_note="需要 OAuth2 应用",
)
# Assert
assert meta.key == "hubspot"
assert meta.display_name == "HubSpot"
assert meta.description == "CRM integration"
assert meta.adapter_type == "http"
assert meta.source_types == ["hubspot"]
assert meta.supported_auth_types == ["oauth2"]
assert meta.tags == ["crm", "marketing"]
assert meta.icon == "hubspot"
assert meta.docs_url == "https://docs.hubspot.com"
assert meta.connection_extra_schema == {"type": "object"}
assert meta.example_config == {"host": "api.hubspot.com"}
assert meta.dependency_note == "需要 OAuth2 应用"
@pytest.mark.unit
def test_integrationMetadata_defaults_appliedWhenOptionalOmitted():
# Arrange / Act - 仅传必填字段
meta = IntegrationMetadata(**_metadata_kwargs())
# Assert - 可选字段取默认值
assert meta.description == ""
assert meta.supported_auth_types == []
assert meta.tags == []
assert meta.icon is None
assert meta.docs_url is None
assert meta.connection_extra_schema is None
assert meta.example_config is None
assert meta.dependency_note is None
@pytest.mark.unit
def test_integrationMetadata_extraField_forbidden_raises():
# Arrange / Act / Assert
with pytest.raises(ValidationError) as exc_info:
IntegrationMetadata(**_metadata_kwargs(unknown_field="x"))
assert "extra" in str(exc_info.value).lower()
# ─── IntegrationMetadata 约束校验 ──────────────────────────────────────────
@pytest.mark.unit
def test_integrationMetadata_key_empty_raises():
# Arrange / Act / Assert - min_length=1
with pytest.raises(ValidationError):
IntegrationMetadata(**_metadata_kwargs(key=""))
@pytest.mark.unit
def test_integrationMetadata_key_exceeds_maxLength_raises():
# Arrange / Act / Assert - max_length=64
with pytest.raises(ValidationError):
IntegrationMetadata(**_metadata_kwargs(key="a" * 65))
@pytest.mark.unit
def test_integrationMetadata_key_maxLength_boundary_accepted():
# Arrange / Act - 边界值 64 字符应通过
meta = IntegrationMetadata(**_metadata_kwargs(key="a" * 64))
# Assert
assert len(meta.key) == 64
@pytest.mark.unit
def test_integrationMetadata_displayName_empty_raises():
# Arrange / Act / Assert - min_length=1
with pytest.raises(ValidationError):
IntegrationMetadata(**_metadata_kwargs(display_name=""))
@pytest.mark.unit
def test_integrationMetadata_displayName_exceeds_maxLength_raises():
# Arrange / Act / Assert - max_length=128
with pytest.raises(ValidationError):
IntegrationMetadata(**_metadata_kwargs(display_name="a" * 129))
@pytest.mark.unit
def test_integrationMetadata_description_exceeds_maxLength_raises():
# Arrange / Act / Assert - max_length=512
with pytest.raises(ValidationError):
IntegrationMetadata(**_metadata_kwargs(description="a" * 513))
@pytest.mark.unit
def test_integrationMetadata_adapterType_exceeds_maxLength_raises():
# Arrange / Act / Assert - max_length=32
with pytest.raises(ValidationError):
IntegrationMetadata(**_metadata_kwargs(adapter_type="a" * 33))
@pytest.mark.unit
def test_integrationMetadata_sourceTypes_empty_raises():
# Arrange / Act / Assert - min_length=1
with pytest.raises(ValidationError):
IntegrationMetadata(**_metadata_kwargs(source_types=[]))
@pytest.mark.unit
def test_integrationMetadata_missing_required_field_raises():
# Arrange / Act / Assert - 缺少 key
with pytest.raises(ValidationError):
IntegrationMetadata(
display_name="x",
adapter_type="http",
source_types=["x"],
)
# ─── GeneratedToolsDraft 字段声明 ─────────────────────────────────────────
@pytest.mark.unit
def test_generatedToolsDraft_fields_matchExpected():
# Arrange / Act
draft = GeneratedToolsDraft(
tool_configs=[{"slug": "tool-a"}, {"slug": "tool-b"}],
override_existing=True,
)
# Assert
assert draft.tool_configs == [{"slug": "tool-a"}, {"slug": "tool-b"}]
assert draft.override_existing is True
@pytest.mark.unit
def test_generatedToolsDraft_overrideExisting_defaultsFalse():
# Arrange / Act - 仅传必填字段
draft = GeneratedToolsDraft(tool_configs=[{"slug": "tool-a"}])
# Assert
assert draft.override_existing is False
@pytest.mark.unit
def test_generatedToolsDraft_toolConfigs_required_raises():
# Arrange / Act / Assert - 缺少 tool_configs
with pytest.raises(ValidationError):
GeneratedToolsDraft() # type: ignore[call-arg]
@pytest.mark.unit
def test_generatedToolsDraft_extraField_forbidden_raises():
# Arrange / Act / Assert
with pytest.raises(ValidationError) as exc_info:
GeneratedToolsDraft(tool_configs=[], unknown="x")
assert "extra" in str(exc_info.value).lower()
@pytest.mark.unit
def test_generatedToolsDraft_emptyToolConfigs_accepted():
# Arrange / Act - 空列表合法(无 min_length 约束)
draft = GeneratedToolsDraft(tool_configs=[])
# Assert
assert draft.tool_configs == []