新增了插件相关的完整领域模型、应用服务、基础设施实现,包括: 1. 插件状态、注册模式、来源等基础枚举和数据结构 2. 插件清单解析、发现、加载工具类 3. 插件注册表领域服务和内存存储实现 4. 插件相关的命令、查询、事件定义 5. 插件REST API接口和DTO映射 6. 集成了原有通道适配器到插件系统 7. 新增内置插件注册和自动发现能力
177 lines
6.2 KiB
Python
177 lines
6.2 KiB
Python
"""
|
|
Integration tests for channel bindings CRUD endpoints (/channel/bindings).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
|
|
|
_BINDING_SEED = uuid.uuid4().hex[:8]
|
|
|
|
|
|
async def _create_binding(test_client, headers, *, channel_type="web", agent_config_id=1):
|
|
account_id = f"intg_test_{_BINDING_SEED}"
|
|
group_id = f"group_{_BINDING_SEED}"
|
|
response = await test_client.post(
|
|
"/channel/bindings",
|
|
json={
|
|
"channel_type": channel_type,
|
|
"account_id": account_id,
|
|
"group_id": group_id,
|
|
"agent_config_id": agent_config_id,
|
|
},
|
|
headers=headers,
|
|
)
|
|
return response
|
|
|
|
|
|
async def test_create_binding_returns_201(test_client, channel_auth_headers):
|
|
response = await _create_binding(test_client, channel_auth_headers)
|
|
if response.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert response.status_code == 201, response.text
|
|
payload = response.json()
|
|
assert "id" in payload
|
|
assert payload["channel_type"] == "web"
|
|
assert payload["is_enabled"] is True
|
|
|
|
await test_client.delete(f"/channel/bindings/{payload['id']}", headers=channel_auth_headers)
|
|
|
|
|
|
async def test_create_binding_response_has_required_fields(test_client, channel_auth_headers):
|
|
response = await _create_binding(test_client, channel_auth_headers)
|
|
if response.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert response.status_code == 201, response.text
|
|
payload = response.json()
|
|
for field in ("id", "channel_type", "account_id", "group_id", "agent_config_id", "is_enabled", "created_at"):
|
|
assert field in payload, f"missing field: {field}"
|
|
|
|
await test_client.delete(f"/channel/bindings/{payload['id']}", headers=channel_auth_headers)
|
|
|
|
|
|
async def test_create_binding_with_empty_channel_type_returns_422(test_client, channel_auth_headers):
|
|
response = await test_client.post(
|
|
"/channel/bindings",
|
|
json={
|
|
"channel_type": "",
|
|
"account_id": "test_acct",
|
|
"group_id": "",
|
|
"agent_config_id": 1,
|
|
},
|
|
headers=channel_auth_headers,
|
|
)
|
|
if response.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert response.status_code == 422
|
|
|
|
|
|
async def test_create_binding_with_invalid_agent_config_id_returns_422(test_client, channel_auth_headers):
|
|
response = await test_client.post(
|
|
"/channel/bindings",
|
|
json={
|
|
"channel_type": "web",
|
|
"account_id": "test_acct",
|
|
"group_id": "",
|
|
"agent_config_id": 0,
|
|
},
|
|
headers=channel_auth_headers,
|
|
)
|
|
if response.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert response.status_code == 422
|
|
|
|
|
|
async def test_list_bindings_returns_paginated_result(test_client, channel_auth_headers):
|
|
response = await test_client.get("/channel/bindings", headers=channel_auth_headers)
|
|
if response.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert response.status_code == 200, response.text
|
|
payload = response.json()
|
|
assert "items" in payload
|
|
assert "meta" in payload
|
|
meta = payload["meta"]
|
|
for field in ("total", "offset", "limit"):
|
|
assert field in meta, f"missing meta field: {field}"
|
|
|
|
|
|
async def test_list_bindings_filter_by_channel_type(test_client, channel_auth_headers):
|
|
response = await test_client.get(
|
|
"/channel/bindings",
|
|
params={"channel_type": "web"},
|
|
headers=channel_auth_headers,
|
|
)
|
|
if response.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert response.status_code == 200, response.text
|
|
payload = response.json()
|
|
for item in payload.get("items", []):
|
|
assert item["channel_type"] == "web"
|
|
|
|
|
|
async def test_list_bindings_pagination(test_client, channel_auth_headers):
|
|
response = await test_client.get(
|
|
"/channel/bindings",
|
|
params={"offset": 0, "limit": 1},
|
|
headers=channel_auth_headers,
|
|
)
|
|
if response.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert response.status_code == 200, response.text
|
|
payload = response.json()
|
|
assert payload["meta"]["limit"] == 1
|
|
assert len(payload["items"]) <= 1
|
|
|
|
|
|
async def test_delete_binding_returns_ok(test_client, channel_auth_headers):
|
|
create_resp = await _create_binding(test_client, channel_auth_headers)
|
|
if create_resp.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert create_resp.status_code == 201, create_resp.text
|
|
binding_id = create_resp.json()["id"]
|
|
|
|
delete_resp = await test_client.delete(
|
|
f"/channel/bindings/{binding_id}",
|
|
headers=channel_auth_headers,
|
|
)
|
|
assert delete_resp.status_code == 200, delete_resp.text
|
|
assert delete_resp.json()["ok"] is True
|
|
|
|
|
|
async def test_delete_nonexistent_binding_returns_404(test_client, channel_auth_headers):
|
|
response = await test_client.delete(
|
|
"/channel/bindings/99999999",
|
|
headers=channel_auth_headers,
|
|
)
|
|
if response.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert response.status_code == 404
|
|
|
|
|
|
async def test_binding_full_lifecycle(test_client, channel_auth_headers):
|
|
create_resp = await _create_binding(test_client, channel_auth_headers)
|
|
if create_resp.status_code == 503:
|
|
pytest.skip("Channel gateway not initialized")
|
|
assert create_resp.status_code == 201, create_resp.text
|
|
binding = create_resp.json()
|
|
binding_id = binding["id"]
|
|
|
|
list_resp = await test_client.get("/channel/bindings", headers=channel_auth_headers)
|
|
assert list_resp.status_code == 200
|
|
ids = [b["id"] for b in list_resp.json()["items"]]
|
|
assert binding_id in ids
|
|
|
|
delete_resp = await test_client.delete(
|
|
f"/channel/bindings/{binding_id}",
|
|
headers=channel_auth_headers,
|
|
)
|
|
assert delete_resp.status_code == 200
|
|
|
|
list_resp2 = await test_client.get("/channel/bindings", headers=channel_auth_headers)
|
|
ids2 = [b["id"] for b in list_resp2.json()["items"]]
|
|
assert binding_id not in ids2
|