2026-07-02 03:25:27 +08:00
|
|
|
|
"""Integration tests for channels capability_router endpoints.
|
|
|
|
|
|
|
2026-07-08 12:58:43 +08:00
|
|
|
|
覆盖能力查询域(CAP-01 列出全部渠道能力 / CAP-02 查询单渠道能力 /
|
|
|
|
|
|
CAP-03 能力画像矩阵)的鉴权矩阵与渠道类型校验。``ChannelType`` 为路径
|
|
|
|
|
|
参数(str 子类,无白名单),未注册插件的渠道类型返回 404。``/capabilities
|
|
|
|
|
|
/matrix`` 静态路径先于 ``/capabilities/{channel_type}`` 声明,避免 "matrix"
|
|
|
|
|
|
被捕获为 channel_type(规范 §6.5)。
|
2026-07-02 03:25:27 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import httpx
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
from .conftest import BASE_URL, DEFAULT_CHANNEL_TYPE
|
|
|
|
|
|
|
|
|
|
|
|
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
|
|
|
|
|
|
|
|
|
|
|
CAPABILITIES_URL = f"{BASE_URL}/capabilities"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# === Auth three-tier for GET /capabilities ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_list_capabilities_requires_auth(test_client: httpx.AsyncClient):
|
|
|
|
|
|
# Act
|
|
|
|
|
|
response = await test_client.get(CAPABILITIES_URL)
|
|
|
|
|
|
# Assert
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-07 16:27:06 +08:00
|
|
|
|
async def test_list_capabilities_requires_admin(test_client: httpx.AsyncClient, standard_user):
|
2026-07-02 03:25:27 +08:00
|
|
|
|
# Act
|
|
|
|
|
|
response = await test_client.get(CAPABILITIES_URL, headers=standard_user["headers"])
|
|
|
|
|
|
# Assert
|
|
|
|
|
|
assert response.status_code == 403
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_admin_can_list_capabilities(test_client: httpx.AsyncClient, admin_headers):
|
|
|
|
|
|
# Act
|
|
|
|
|
|
response = await test_client.get(CAPABILITIES_URL, headers=admin_headers)
|
|
|
|
|
|
# Assert
|
|
|
|
|
|
assert response.status_code == 200, response.text
|
|
|
|
|
|
|
|
|
|
|
|
payload = response.json()
|
|
|
|
|
|
assert payload["success"] is True
|
|
|
|
|
|
assert isinstance(payload["data"], dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-08 12:58:43 +08:00
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# === GET /capabilities/matrix — CAP-03 capability matrix ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_list_capability_matrix_requires_auth(test_client: httpx.AsyncClient):
|
|
|
|
|
|
# Act
|
|
|
|
|
|
response = await test_client.get(f"{CAPABILITIES_URL}/matrix")
|
|
|
|
|
|
# Assert
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_list_capability_matrix_requires_admin(test_client: httpx.AsyncClient, standard_user):
|
|
|
|
|
|
# Act
|
|
|
|
|
|
response = await test_client.get(f"{CAPABILITIES_URL}/matrix", headers=standard_user["headers"])
|
|
|
|
|
|
# Assert
|
|
|
|
|
|
assert response.status_code == 403
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_admin_can_list_capability_matrix(test_client: httpx.AsyncClient, admin_headers):
|
|
|
|
|
|
# Act
|
|
|
|
|
|
response = await test_client.get(f"{CAPABILITIES_URL}/matrix", headers=admin_headers)
|
|
|
|
|
|
# Assert
|
|
|
|
|
|
assert response.status_code == 200, response.text
|
|
|
|
|
|
|
|
|
|
|
|
payload = response.json()
|
|
|
|
|
|
assert payload["success"] is True
|
|
|
|
|
|
assert isinstance(payload["data"], dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_matrix_static_path_not_captured_as_channel_type(test_client: httpx.AsyncClient, admin_headers):
|
|
|
|
|
|
# Act — /capabilities/matrix 是静态路径,必须先于 /capabilities/{channel_type}
|
|
|
|
|
|
# 声明,避免 "matrix" 被捕获为 channel_type。若路由顺序错误,此请求会走
|
|
|
|
|
|
# getCapability("matrix") 并返回 404(未注册插件),而非 matrix 端点的 200。
|
|
|
|
|
|
response = await test_client.get(f"{CAPABILITIES_URL}/matrix", headers=admin_headers)
|
|
|
|
|
|
# Assert — 命中 matrix 端点返回 200,证明静态路径未被动态参数捕获
|
|
|
|
|
|
assert response.status_code == 200, response.text
|
|
|
|
|
|
assert response.json()["success"] is True
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-02 03:25:27 +08:00
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# === GET /capabilities/{channel_type} ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-07 16:27:06 +08:00
|
|
|
|
async def test_admin_can_get_capability_for_channel_type(test_client: httpx.AsyncClient, admin_headers):
|
2026-07-02 03:25:27 +08:00
|
|
|
|
# Act — DEFAULT_CHANNEL_TYPE 已注册则 200,未注册则 404
|
2026-07-07 16:27:06 +08:00
|
|
|
|
response = await test_client.get(f"{CAPABILITIES_URL}/{DEFAULT_CHANNEL_TYPE}", headers=admin_headers)
|
2026-07-02 03:25:27 +08:00
|
|
|
|
# Assert
|
|
|
|
|
|
assert response.status_code in (200, 404), response.text
|
|
|
|
|
|
|
|
|
|
|
|
if response.status_code == 200:
|
|
|
|
|
|
payload = response.json()
|
|
|
|
|
|
assert payload["success"] is True
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-07 16:27:06 +08:00
|
|
|
|
async def test_get_capability_unregistered_channel_type(test_client: httpx.AsyncClient, admin_headers):
|
2026-07-04 00:18:04 +08:00
|
|
|
|
# Act — "foo" 是合法字符串但未注册插件
|
2026-07-02 03:25:27 +08:00
|
|
|
|
response = await test_client.get(f"{CAPABILITIES_URL}/foo", headers=admin_headers)
|
2026-07-04 00:18:04 +08:00
|
|
|
|
# Assert - channel_type 无白名单,任何字符串都合法,但未注册插件返回 404
|
|
|
|
|
|
assert response.status_code == 404, response.text
|