ForcePilot/backend/test/unit/external_systems/integrations/test_registry.py
Kris 4c2c6b956d test: 批量修复并新增单元/集成测试用例
1.  格式化代码与参数换行,提升可读性
2.  修正测试断言与测试逻辑,覆盖更多边界场景
3.  新增外部系统告警仓库、工具服务、系统服务等测试用例
4.  完善插件注册表的重启状态检测逻辑测试
5.  更新测试断言计数与预期结果,匹配代码变更
6.  修复Instagram适配器的签名校验测试逻辑
7.  优化会话合并、上下文测试的类型检查与断言
2026-07-11 15:51:28 +08:00

407 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""integrations/registry.py 厂商集成目录注册表单元测试。
覆盖 register / filter_integrations / list_integrations / count_integrations /
get_integration / get_integration_or_raise / list_adapter_types / clear。
全局状态_integrations通过 _snapshot_registry fixture 在测试后恢复。
"""
from __future__ import annotations
from typing import Any
import pytest
from yuxi.external_systems.exceptions import (
DomainValidationError,
EntityNotFoundError,
)
from yuxi.external_systems.integrations.registry import IntegrationRegistry
from yuxi.external_systems.integrations.schemas import IntegrationMetadata
# ─── 全局状态快照 fixture ─────────────────────────────────────────────────
@pytest.fixture
def _snapshot_registry():
"""保存并恢复 _integrations避免污染其他测试。"""
saved = dict(IntegrationRegistry._integrations)
IntegrationRegistry._integrations.clear()
try:
yield
finally:
IntegrationRegistry._integrations.clear()
IntegrationRegistry._integrations.update(saved)
# ─── 辅助构造 ─────────────────────────────────────────────────────────────
def _make_metadata(**overrides: Any) -> IntegrationMetadata:
"""构造测试用 IntegrationMetadata。"""
base: dict[str, Any] = {
"key": "test",
"display_name": "Test Integration",
"description": "A test integration",
"adapter_type": "http",
"source_types": ["test"],
}
base.update(overrides)
return IntegrationMetadata(**base)
# ─── register ─────────────────────────────────────────────────────────────
@pytest.mark.unit
def test_register_adds_metadata_to_registry(_snapshot_registry):
# Arrange
meta = _make_metadata(key="salesforce")
# Act
IntegrationRegistry.register(meta)
# Assert
assert IntegrationRegistry.get_integration("salesforce") is meta
@pytest.mark.unit
def test_register_idempotent_with_same_instance(_snapshot_registry):
# Arrange
meta = _make_metadata(key="salesforce")
IntegrationRegistry.register(meta)
# Act - 同一实例重复注册不应抛异常
IntegrationRegistry.register(meta)
# Assert
assert IntegrationRegistry.get_integration("salesforce") is meta
@pytest.mark.unit
def test_register_conflict_with_different_instance_raises(_snapshot_registry):
# Arrange
first = _make_metadata(key="salesforce", display_name="First")
IntegrationRegistry.register(first)
second = _make_metadata(key="salesforce", display_name="Second")
# Act / Assert - 不同实例重复注册应抛 DomainValidationError
with pytest.raises(DomainValidationError) as exc_info:
IntegrationRegistry.register(second)
assert "salesforce" in str(exc_info.value)
assert "First" in str(exc_info.value)
assert "Second" in str(exc_info.value)
# 原注册项保留
assert IntegrationRegistry.get_integration("salesforce") is first
# ─── filter_integrations ──────────────────────────────────────────────────
@pytest.mark.unit
def test_filterIntegrations_returns_all_sorted_by_key(_snapshot_registry):
# Arrange - 故意乱序注册
IntegrationRegistry.register(_make_metadata(key="zoho"))
IntegrationRegistry.register(_make_metadata(key="apple"))
IntegrationRegistry.register(_make_metadata(key="microsoft"))
# Act
result = IntegrationRegistry.filter_integrations()
# Assert - 按 key 升序稳定排序
assert [it.key for it in result] == ["apple", "microsoft", "zoho"]
@pytest.mark.unit
def test_filterIntegrations_by_adapter_type(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf", adapter_type="http"))
IntegrationRegistry.register(_make_metadata(key="grpc_pkg", adapter_type="grpc"))
IntegrationRegistry.register(_make_metadata(key="hub", adapter_type="http"))
# Act
result = IntegrationRegistry.filter_integrations(adapter_type="http")
# Assert - 精确匹配 adapter_type
assert [it.key for it in result] == ["hub", "sf"]
@pytest.mark.unit
def test_filterIntegrations_by_tags_or_relation(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf", tags=["crm", "sales"]))
IntegrationRegistry.register(_make_metadata(key="slack", tags=["messaging"]))
IntegrationRegistry.register(_make_metadata(key="hub", tags=["crm", "marketing"]))
# Act - 命中任一标签即返回OR 关系)
result = IntegrationRegistry.filter_integrations(tags=["sales", "messaging"])
# Assert
assert [it.key for it in result] == ["sf", "slack"]
@pytest.mark.unit
def test_filterIntegrations_by_keyword_case_insensitive(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf", display_name="Salesforce"))
IntegrationRegistry.register(_make_metadata(key="hub", display_name="HubSpot"))
# Act - 大小写不敏感匹配 display_name
result = IntegrationRegistry.filter_integrations(keyword="SALES")
# Assert
assert [it.key for it in result] == ["sf"]
@pytest.mark.unit
def test_filterIntegrations_by_keyword_matches_description(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf", display_name="SF", description="CRM platform"))
IntegrationRegistry.register(_make_metadata(key="slack", display_name="Slack", description="Chat tool"))
# Act - 关键词匹配 description
result = IntegrationRegistry.filter_integrations(keyword="crm")
# Assert
assert [it.key for it in result] == ["sf"]
@pytest.mark.unit
def test_filterIntegrations_combined_filters(_snapshot_registry):
# Arrange - sf/hub 同时满足三条件slack 仅满足 adapter_type+keyword 不满足 tags
IntegrationRegistry.register(
_make_metadata(key="sf", adapter_type="http", tags=["crm"], display_name="Salesforce CRM")
)
IntegrationRegistry.register(
_make_metadata(key="hub", adapter_type="http", tags=["crm"], display_name="HubSpot CRM")
)
IntegrationRegistry.register(
_make_metadata(key="slack", adapter_type="http", tags=["messaging"], display_name="Slack CRM")
)
# Act - adapter_type + tags + keyword 组合AND 关系)
result = IntegrationRegistry.filter_integrations(adapter_type="http", tags=["crm"], keyword="crm")
# Assert - 同时满足所有条件sf/hub 命中slack 因 tags 不匹配被排除
assert [it.key for it in result] == ["hub", "sf"]
@pytest.mark.unit
def test_filterIntegrations_empty_when_no_match(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf"))
# Act
result = IntegrationRegistry.filter_integrations(keyword="nonexistent")
# Assert
assert result == []
@pytest.mark.unit
def test_filterIntegrations_tolerates_none_fields(_snapshot_registry):
# Arrange - 模拟注册包异常导致字段为空,确保过滤不抛 500
meta = _make_metadata(key="sf", display_name="Salesforce")
IntegrationRegistry.register(meta)
meta.description = None
meta.tags = None
# Act / Assert - keyword/tags 过滤均不应崩溃
result_by_keyword = IntegrationRegistry.filter_integrations(keyword="sales")
result_by_tags = IntegrationRegistry.filter_integrations(tags=["crm"])
assert [it.key for it in result_by_keyword] == ["sf"]
assert result_by_tags == []
# ─── list_integrations ────────────────────────────────────────────────────
@pytest.mark.unit
def test_listIntegrations_paginates_results(_snapshot_registry):
# Arrange
for k in ("a", "b", "c", "d", "e"):
IntegrationRegistry.register(_make_metadata(key=k))
# Act - 第 2 页,每页 2 条
result = IntegrationRegistry.list_integrations(limit=2, offset=2)
# Assert
assert [it.key for it in result] == ["c", "d"]
@pytest.mark.unit
def test_listIntegrations_offset_negative_clamped_to_zero(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf"))
# Act - offset 为负数应归零
result = IntegrationRegistry.list_integrations(limit=10, offset=-5)
# Assert
assert [it.key for it in result] == ["sf"]
@pytest.mark.unit
def test_listIntegrations_limit_negative_clamped_to_zero(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf"))
# Act - limit 为负数应归零,返回空列表
result = IntegrationRegistry.list_integrations(limit=-1, offset=0)
# Assert
assert result == []
@pytest.mark.unit
def test_listIntegrations_offset_beyond_length_returns_empty(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf"))
# Act - offset 超过总数
result = IntegrationRegistry.list_integrations(limit=10, offset=100)
# Assert
assert result == []
@pytest.mark.unit
def test_listIntegrations_defaults_limit50_offset0(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf"))
# Act - 不传分页参数
result = IntegrationRegistry.list_integrations()
# Assert
assert [it.key for it in result] == ["sf"]
# ─── count_integrations ───────────────────────────────────────────────────
@pytest.mark.unit
def test_countIntegrations_returns_total(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf"))
IntegrationRegistry.register(_make_metadata(key="hub"))
# Act
total = IntegrationRegistry.count_integrations()
# Assert
assert total == 2
@pytest.mark.unit
def test_countIntegrations_respects_filters(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf", adapter_type="http"))
IntegrationRegistry.register(_make_metadata(key="grpc_pkg", adapter_type="grpc"))
# Act
total = IntegrationRegistry.count_integrations(adapter_type="http")
# Assert
assert total == 1
@pytest.mark.unit
def test_countIntegrations_zero_when_no_match(_snapshot_registry):
# Arrange / Act / Assert
assert IntegrationRegistry.count_integrations(keyword="nonexistent") == 0
# ─── get_integration ─────────────────────────────────────────────────────
@pytest.mark.unit
def test_getIntegration_returns_metadata(_snapshot_registry):
# Arrange
meta = _make_metadata(key="salesforce")
IntegrationRegistry.register(meta)
# Act
result = IntegrationRegistry.get_integration("salesforce")
# Assert
assert result is meta
@pytest.mark.unit
def test_getIntegration_unknown_returns_none(_snapshot_registry):
# Arrange / Act / Assert
assert IntegrationRegistry.get_integration("nonexistent") is None
# ─── get_integration_or_raise ──────────────────────────────────────────────
@pytest.mark.unit
def test_getIntegrationOrRaise_returns_metadata(_snapshot_registry):
# Arrange
meta = _make_metadata(key="salesforce")
IntegrationRegistry.register(meta)
# Act
result = IntegrationRegistry.get_integration_or_raise("salesforce")
# Assert
assert result is meta
@pytest.mark.unit
def test_getIntegrationOrRaise_unknown_raises_entityNotFoundError(_snapshot_registry):
# Arrange / Act / Assert
with pytest.raises(EntityNotFoundError) as exc_info:
IntegrationRegistry.get_integration_or_raise("nonexistent")
assert "nonexistent" in str(exc_info.value)
# ─── list_adapter_types ───────────────────────────────────────────────────
@pytest.mark.unit
def test_listAdapterTypes_returns_dedup_types(_snapshot_registry):
# Arrange - 多个集成共用 adapter_type
IntegrationRegistry.register(_make_metadata(key="sf", adapter_type="http"))
IntegrationRegistry.register(_make_metadata(key="hub", adapter_type="http"))
IntegrationRegistry.register(_make_metadata(key="grpc_pkg", adapter_type="grpc"))
# Act
types = IntegrationRegistry.list_adapter_types()
# Assert - 去重后按字母升序排序返回
assert types == ["grpc", "http"]
@pytest.mark.unit
def test_listAdapterTypes_empty_when_nothing_registered(_snapshot_registry):
# Arrange / Act / Assert
assert IntegrationRegistry.list_adapter_types() == []
# ─── clear ────────────────────────────────────────────────────────────────
@pytest.mark.unit
def test_clear_empties_integrations(_snapshot_registry):
# Arrange
IntegrationRegistry.register(_make_metadata(key="sf"))
# Act
IntegrationRegistry.clear()
# Assert
assert IntegrationRegistry.list_adapter_types() == []
assert IntegrationRegistry.get_integration("sf") is None
assert IntegrationRegistry.count_integrations() == 0
@pytest.mark.unit
def test_clear_safe_when_already_empty(_snapshot_registry):
# Arrange / Act / Assert - 空注册表清空不抛异常
IntegrationRegistry.clear()
assert IntegrationRegistry.count_integrations() == 0