Files
opsxsql/backend/tests/test_ldap_config.py
T
logan 4c9427e546 sync: 同步 sql-platform-pro 最新代码 — 权限体系安全修复 v0.8.1
- P0: 实例访问控制全线接入权限组 (829测试)
- P1: permissions字段激活 + 操作级权限检查 (843测试)
- 前端: 删除数据权限页面, 权限组合并为唯一入口
- 新增: CodeMirror编辑器 + 查询模板 + 慢查询分析
- 新增: MongoDB管理页面 + Dashboard增强
- 修复: Pydantic V2迁移 + 前端TS错误修复
2026-05-23 14:41:06 +08:00

365 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.
"""LDAP 配置管理端点测试 — 覆盖 GET/PUT /api/v1/ldap-config 和 POST /test"""
import pytest
from httpx import AsyncClient
from tests.conftest import auth_header
# ── 合法的 LDAP 配置载荷 ──────────────────────────────────────────
VALID_LDAP_PAYLOAD = {
"ldap_host": "ldap.example.com",
"ldap_port": 389,
"use_ldaps": False,
"admin_dn": "cn=admin,dc=example,dc=com",
"admin_password": "admin_password_123",
"search_filter": "(uid=%s)",
"base_dn": "ou=users,dc=example,dc=com",
"user_mapping": {"username": "uid", "display_name": "cn", "email": "mail"},
}
# ────────────────────────────────────────────────────────────────────
# 1. 权限校验:非 admin 角色应被拒绝 (403)
# ────────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_get_ldap_config_developer_forbidden(
client: AsyncClient, developer_token: str
):
"""developer 角色访问 GET /ldap-config 应返回 403"""
resp = await client.get(
"/api/v1/ldap-config",
headers=auth_header(developer_token),
)
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_put_ldap_config_developer_forbidden(
client: AsyncClient, developer_token: str
):
"""developer 角色访问 PUT /ldap-config 应返回 403"""
resp = await client.put(
"/api/v1/ldap-config",
json=VALID_LDAP_PAYLOAD,
headers=auth_header(developer_token),
)
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_test_ldap_connection_developer_forbidden(
client: AsyncClient, developer_token: str
):
"""developer 角色访问 POST /ldap-config/test 应返回 403"""
resp = await client.post(
"/api/v1/ldap-config/test",
json={"test_user": "zhangsan", "test_password": "pass"},
headers=auth_header(developer_token),
)
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_ldap_endpoints_require_authentication(client: AsyncClient):
"""未携带 Token 访问任何 LDAP 端点应返回 401"""
resp_get = await client.get("/api/v1/ldap-config")
assert resp_get.status_code in (401, 403)
resp_put = await client.put("/api/v1/ldap-config", json=VALID_LDAP_PAYLOAD)
assert resp_put.status_code in (401, 403)
resp_test = await client.post(
"/api/v1/ldap-config/test",
json={"test_user": "u", "test_password": "p"},
)
assert resp_test.status_code in (401, 403)
# ────────────────────────────────────────────────────────────────────
# 2. PUT 创建配置(首次 upsert)
# ────────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_create_ldap_config_success(client: AsyncClient, admin_token: str):
"""admin 首次 PUT 创建 LDAP 配置,返回 200 且字段正确"""
resp = await client.put(
"/api/v1/ldap-config",
json=VALID_LDAP_PAYLOAD,
headers=auth_header(admin_token),
)
assert resp.status_code == 200
data = resp.json()
# 基本字段断言
assert data["ldap_host"] == "ldap.example.com"
assert data["ldap_port"] == 389
assert data["use_ldaps"] is False
assert data["admin_dn"] == "cn=admin,dc=example,dc=com"
assert data["search_filter"] == "(uid=%s)"
assert data["base_dn"] == "ou=users,dc=example,dc=com"
# user_mapping 应被解析为 JSON dict
assert data["user_mapping"]["username"] == "uid"
assert data["user_mapping"]["display_name"] == "cn"
assert data["user_mapping"]["email"] == "mail"
# 时间戳字段应存在
assert data["created_at"] is not None
assert data["updated_at"] is not None
# ────────────────────────────────────────────────────────────────────
# 3. 密码脱敏:返回值中密码应为 ******
# ────────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_ldap_config_password_masked(client: AsyncClient, admin_token: str):
"""创建配置后返回的 admin_password 必须脱敏为 ******"""
resp = await client.put(
"/api/v1/ldap-config",
json={
"ldap_host": "ldap.mask.com",
"ldap_port": 389,
"admin_dn": "cn=admin,dc=mask,dc=com",
"admin_password": "super_secret_密码",
"base_dn": "dc=mask,dc=com",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 200
assert resp.json()["admin_password"] == "******"
# ────────────────────────────────────────────────────────────────────
# 4. GET 获取配置(已存在 / 不存在)
# ────────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_get_ldap_config_after_create(client: AsyncClient, admin_token: str):
"""先创建,再 GET 应返回相同配置"""
await client.put(
"/api/v1/ldap-config",
json=VALID_LDAP_PAYLOAD,
headers=auth_header(admin_token),
)
resp = await client.get(
"/api/v1/ldap-config",
headers=auth_header(admin_token),
)
assert resp.status_code == 200
assert resp.json()["ldap_host"] == "ldap.example.com"
@pytest.mark.asyncio
async def test_get_ldap_config_not_found(client: AsyncClient, admin_token: str):
"""未创建配置时 GET 应返回 404"""
resp = await client.get(
"/api/v1/ldap-config",
headers=auth_header(admin_token),
)
assert resp.status_code == 404
# ────────────────────────────────────────────────────────────────────
# 5. PUT 更新配置(第二次 upsert)
# ────────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_update_existing_ldap_config(client: AsyncClient, admin_token: str):
"""对已有配置再次 PUT 应覆盖更新"""
# 第一次创建
await client.put(
"/api/v1/ldap-config",
json={
"ldap_host": "ldap.old.com",
"ldap_port": 389,
"admin_dn": "cn=admin,dc=old,dc=com",
"admin_password": "oldpass",
"base_dn": "dc=old,dc=com",
},
headers=auth_header(admin_token),
)
# 第二次更新
resp = await client.put(
"/api/v1/ldap-config",
json={
"ldap_host": "ldap.new.com",
"ldap_port": 636,
"use_ldaps": True,
"admin_dn": "cn=admin,dc=new,dc=com",
"admin_password": "newpass",
"search_filter": "(sAMAccountName=%s)",
"base_dn": "dc=new,dc=com",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 200
data = resp.json()
assert data["ldap_host"] == "ldap.new.com"
assert data["ldap_port"] == 636
assert data["use_ldaps"] is True
assert data["search_filter"] == "(sAMAccountName=%s)"
# ────────────────────────────────────────────────────────────────────
# 6. 输入校验:非法字段值应返回 422
# ────────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_create_ldap_config_missing_required_fields(
client: AsyncClient, admin_token: str
):
"""缺少必填字段(ldap_host)应返回 422"""
resp = await client.put(
"/api/v1/ldap-config",
json={
"ldap_port": 389,
"admin_dn": "cn=admin,dc=example,dc=com",
"admin_password": "pass",
"base_dn": "dc=example,dc=com",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 422
@pytest.mark.asyncio
async def test_create_ldap_config_port_out_of_range(
client: AsyncClient, admin_token: str
):
"""端口号超出范围 (>65535) 应返回 422"""
payload = {**VALID_LDAP_PAYLOAD, "ldap_port": 99999}
resp = await client.put(
"/api/v1/ldap-config",
json=payload,
headers=auth_header(admin_token),
)
assert resp.status_code == 422
@pytest.mark.asyncio
async def test_create_ldap_config_empty_host(client: AsyncClient, admin_token: str):
"""ldap_host 为空字符串应返回 422"""
payload = {**VALID_LDAP_PAYLOAD, "ldap_host": ""}
resp = await client.put(
"/api/v1/ldap-config",
json=payload,
headers=auth_header(admin_token),
)
assert resp.status_code == 422
@pytest.mark.asyncio
async def test_create_ldap_config_empty_password(client: AsyncClient, admin_token: str):
"""admin_password 为空字符串应返回 422"""
payload = {**VALID_LDAP_PAYLOAD, "admin_password": ""}
resp = await client.put(
"/api/v1/ldap-config",
json=payload,
headers=auth_header(admin_token),
)
assert resp.status_code == 422
# ────────────────────────────────────────────────────────────────────
# 7. POST /test — 测试 LDAP 连接
# ────────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_ldap_connection_mock_success(client: AsyncClient, admin_token: str):
"""配置存在时 POST /test 在 mock 模式下应返回 success=True"""
await client.put(
"/api/v1/ldap-config",
json=VALID_LDAP_PAYLOAD,
headers=auth_header(admin_token),
)
resp = await client.post(
"/api/v1/ldap-config/test",
json={"test_user": "zhangsan", "test_password": "zhang123"},
headers=auth_header(admin_token),
)
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
# mock 模式消息中包含 Mock 字样
assert "Mock" in data["message"]
# 消息中应包含主机和测试用户信息
assert "zhangsan" in data["message"]
@pytest.mark.asyncio
async def test_ldap_connection_test_without_config(
client: AsyncClient, admin_token: str
):
"""未创建配置时 POST /test 应返回 404"""
resp = await client.post(
"/api/v1/ldap-config/test",
json={"test_user": "user1", "test_password": "pass1"},
headers=auth_header(admin_token),
)
assert resp.status_code == 404
@pytest.mark.asyncio
async def test_ldap_connection_test_missing_test_user(
client: AsyncClient, admin_token: str
):
"""POST /test 缺少 test_user 应返回 422"""
await client.put(
"/api/v1/ldap-config",
json=VALID_LDAP_PAYLOAD,
headers=auth_header(admin_token),
)
resp = await client.post(
"/api/v1/ldap-config/test",
json={"test_password": "pass"},
headers=auth_header(admin_token),
)
assert resp.status_code == 422
# ────────────────────────────────────────────────────────────────────
# 8. 可选字段与默认值
# ────────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_create_ldap_config_with_defaults(client: AsyncClient, admin_token: str):
"""仅传必填字段,可选字段应有合理默认值"""
resp = await client.put(
"/api/v1/ldap-config",
json={
"ldap_host": "ldap.defaults.com",
"admin_dn": "cn=admin,dc=defaults,dc=com",
"admin_password": "password123",
"base_dn": "dc=defaults,dc=com",
},
headers=auth_header(admin_token),
)
assert resp.status_code == 200
data = resp.json()
# ldap_port 默认 389
assert data["ldap_port"] == 389
# use_ldaps 默认 False
assert data["use_ldaps"] is False
# search_filter 未传,应为 None
assert data["search_filter"] is None
# user_mapping 未传,应为 None
assert data["user_mapping"] is None
@pytest.mark.asyncio
async def test_ldap_config_use_ldaps_flag(client: AsyncClient, admin_token: str):
"""创建配置时 use_ldaps=True 应正确保存"""
payload = {**VALID_LDAP_PAYLOAD, "use_ldaps": True, "ldap_port": 636}
resp = await client.put(
"/api/v1/ldap-config",
json=payload,
headers=auth_header(admin_token),
)
assert resp.status_code == 200
assert resp.json()["use_ldaps"] is True
assert resp.json()["ldap_port"] == 636