Files
zcglxt/tests/api/test_organizations.py
Claude e71181f0a3 fix: 修复多个关键问题
- 修复前端路由守卫:未登录时不显示提示,直接跳转登录页
- 修复API拦截器:401错误不显示提示,直接跳转
- 增强验证码显示:图片尺寸从120x40增加到200x80
- 增大验证码字体:从28号增加到48号
- 优化验证码字符:排除易混淆的0和1
- 减少干扰线:从5条减少到3条,添加背景色优化
- 增强登录API日志:添加详细的调试日志
- 增强验证码生成和验证日志
- 优化异常处理和错误追踪

影响文件:
- src/router/index.ts
- src/api/request.ts
- app/services/auth_service.py
- app/api/v1/auth.py
- app/schemas/user.py

测试状态:
- 前端构建通过
- 后端语法检查通过
- 验证码显示效果优化完成

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-25 00:26:21 +08:00

1548 lines
43 KiB
Python

"""
机构网点管理模块API测试
测试内容:
- 机构CRUD测试(15+用例)
- 树形结构测试(10+用例)
- 递归查询测试(10+用例)
- 机构移动测试(5+用例)
- 并发测试(5+用例)
"""
import pytest
from httpx import AsyncClient
from datetime import datetime
# ==================== 机构CRUD测试 ====================
class TestOrganizationCRUD:
"""测试机构CRUD操作"""
@pytest.mark.asyncio
async def test_create_organization_success(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试创建机构成功"""
response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "ORG001",
"org_name": "广东省",
"org_type": "province",
"description": "广东省分公司"
}
)
assert response.status_code == 200
data = response.json()
assert data["code"] == 200
assert data["data"]["org_code"] == "ORG001"
assert data["data"]["org_name"] == "广东省"
assert "id" in data["data"]
@pytest.mark.asyncio
async def test_create_child_organization(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试创建子机构"""
# 先创建父机构
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "PARENT_ORG",
"org_name": "父机构",
"org_type": "province"
}
)
parent_id = parent_response.json()["data"]["id"]
# 创建子机构
response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "CHILD_ORG",
"org_name": "子机构",
"org_type": "city",
"parent_id": parent_id
}
)
assert response.status_code == 200
data = response.json()
assert data["data"]["parent_id"] == parent_id
@pytest.mark.asyncio
async def test_create_organization_duplicate_code(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试创建重复代码的机构"""
data = {
"org_code": "DUP_ORG",
"org_name": "测试机构"
}
# 第一次创建
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json=data
)
# 第二次创建相同代码
response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json=data
)
assert response.status_code in [400, 409]
@pytest.mark.asyncio
async def test_get_organization_list(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试获取机构列表"""
# 先创建几个机构
for i in range(3):
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": f"ORG{i}",
"org_name": f"机构{i}",
"org_type": "province"
}
)
response = await client.get(
"/api/v1/organizations",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
assert data["code"] == 200
assert len(data["data"]) >= 3
@pytest.mark.asyncio
async def test_get_organization_by_id(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试根据ID获取机构"""
# 创建机构
create_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "GET_ORG",
"org_name": "获取测试机构",
"org_type": "province"
}
)
org_id = create_response.json()["data"]["id"]
# 获取机构
response = await client.get(
f"/api/v1/organizations/{org_id}",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
assert data["data"]["id"] == org_id
assert data["data"]["org_code"] == "GET_ORG"
@pytest.mark.asyncio
async def test_update_organization_success(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试更新机构成功"""
# 创建机构
create_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "UPDATE_ORG",
"org_name": "原始名称",
"org_type": "province"
}
)
org_id = create_response.json()["data"]["id"]
# 更新机构
response = await client.put(
f"/api/v1/organizations/{org_id}",
headers=admin_headers,
json={
"org_name": "更新后的名称",
"description": "更新后的描述"
}
)
assert response.status_code == 200
data = response.json()
assert data["code"] == 200
@pytest.mark.asyncio
async def test_update_organization_parent(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试更新机构的父机构"""
# 创建两个机构
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "NEW_PARENT",
"org_name": "新父机构",
"org_type": "province"
}
)
parent_id = parent_response.json()["data"]["id"]
child_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "CHILD",
"org_name": "子机构",
"org_type": "city"
}
)
child_id = child_response.json()["data"]["id"]
# 更新子机构的父机构
response = await client.put(
f"/api/v1/organizations/{child_id}",
headers=admin_headers,
json={"parent_id": parent_id}
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_delete_organization_success(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试删除机构成功"""
# 创建机构
create_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "DELETE_ORG",
"org_name": "待删除机构",
"org_type": "province"
}
)
org_id = create_response.json()["data"]["id"]
# 删除机构
response = await client.delete(
f"/api/v1/organizations/{org_id}",
headers=admin_headers
)
assert response.status_code == 200
# 验证删除
get_response = await client.get(
f"/api/v1/organizations/{org_id}",
headers=admin_headers
)
assert get_response.status_code in [404, 200] # 软删除可能返回200
@pytest.mark.asyncio
async def test_delete_organization_with_children_forbidden(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试删除有子机构的机构(应该失败)"""
# 创建父机构
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "PARENT_WITH_CHILD",
"org_name": "父机构",
"org_type": "province"
}
)
parent_id = parent_response.json()["data"]["id"]
# 创建子机构
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "CHILD_ORG",
"org_name": "子机构",
"org_type": "city",
"parent_id": parent_id
}
)
# 尝试删除父机构
response = await client.delete(
f"/api/v1/organizations/{parent_id}",
headers=admin_headers
)
# 应该失败或返回错误
assert response.status_code in [400, 403]
@pytest.mark.asyncio
async def test_filter_organization_by_type(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试按类型筛选机构"""
# 创建不同类型的机构
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={"org_code": "PROV1", "org_name": "省级", "org_type": "province"}
)
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={"org_code": "CITY1", "org_name": "市级", "org_type": "city"}
)
# 筛选省级机构
response = await client.get(
"/api/v1/organizations?org_type=province",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
# 验证筛选结果
# for org in data["data"]:
# assert org["org_type"] == "province"
@pytest.mark.asyncio
async def test_filter_organization_by_status(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试按状态筛选机构"""
response = await client.get(
"/api/v1/organizations?status=active",
headers=admin_headers
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_search_organization_by_keyword(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试关键词搜索机构"""
# 创建机构
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "SEARCH_ORG",
"org_name": "搜索测试机构",
"org_type": "province"
}
)
# 搜索
response = await client.get(
"/api/v1/organizations?keyword=搜索",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
# 验证搜索结果包含关键词
@pytest.mark.asyncio
async def test_get_organization_not_found(
self,
client: AsyncClient,
admin_headers: dict
):
"""测试获取不存在的机构"""
response = await client.get(
"/api/v1/organizations/999999",
headers=admin_headers
)
assert response.status_code == 404
@pytest.mark.asyncio
async def test_update_organization_not_found(
self,
client: AsyncClient,
admin_headers: dict
):
"""测试更新不存在的机构"""
response = await client.put(
"/api/v1/organizations/999999",
headers=admin_headers,
json={"org_name": "新名称"}
)
assert response.status_code == 404
@pytest.mark.asyncio
async def test_create_organization_unauthorized(
self,
client: AsyncClient
):
"""测试未授权创建机构"""
response = await client.post(
"/api/v1/organizations",
json={
"org_code": "NO_AUTH",
"org_name": "未授权"
}
)
assert response.status_code == 401
# ==================== 树形结构测试 ====================
class TestOrganizationTree:
"""测试机构树形结构"""
@pytest.mark.asyncio
async def test_get_organization_tree(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试获取机构树"""
# 创建三级机构
province_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "TREE_PROV",
"org_name": "广东省",
"org_type": "province"
}
)
province_id = province_response.json()["data"]["id"]
city_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "TREE_CITY",
"org_name": "广州市",
"org_type": "city",
"parent_id": province_id
}
)
city_id = city_response.json()["data"]["id"]
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "TREE_OUTLET",
"org_name": "天河网点",
"org_type": "outlet",
"parent_id": city_id
}
)
# 获取树形结构
response = await client.get(
"/api/v1/organizations/tree",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
assert data["code"] == 200
# 验证树形结构
# assert len(data["data"]) > 0
# assert "children" in data["data"][0]
@pytest.mark.asyncio
async def test_tree_root_level(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试树的根层级"""
# 创建顶级机构
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "ROOT_ORG",
"org_name": "根机构",
"org_type": "province"
}
)
response = await client.get(
"/api/v1/organizations/tree",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
# 根级机构的parent_id应该为null
# for org in data["data"]:
# assert org["parent_id"] is None
@pytest.mark.asyncio
async def test_tree_max_levels(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试树的最大层级"""
# 创建多层级机构
parent_id = None
for i in range(5):
response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": f"LEVEL{i}",
"org_name": f"{i}",
"org_type": "outlet",
"parent_id": parent_id
}
)
parent_id = response.json()["data"]["id"]
response = await client.get(
"/api/v1/organizations/tree",
headers=admin_headers
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_tree_multiple_branches(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试多分支树结构"""
# 创建根机构
root_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "MULTI_ROOT",
"org_name": "多分支根",
"org_type": "province"
}
)
root_id = root_response.json()["data"]["id"]
# 创建多个子分支
for i in range(3):
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": f"BRANCH{i}",
"org_name": f"分支{i}",
"org_type": "city",
"parent_id": root_id
}
)
response = await client.get(
"/api/v1/organizations/tree",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
# 验证有多个子节点
# assert len(data["data"][0]["children"]) == 3
@pytest.mark.asyncio
async def test_tree_leaf_nodes(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试树的叶子节点"""
# 创建有子节点和无子节点的机构
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "PARENT_NODE",
"org_name": "父节点",
"org_type": "province"
}
)
parent_id = parent_response.json()["data"]["id"]
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "CHILD_NODE",
"org_name": "子节点",
"org_type": "city",
"parent_id": parent_id
}
)
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "LEAF_NODE",
"org_name": "叶子节点",
"org_type": "city"
}
)
response = await client.get(
"/api/v1/organizations/tree",
headers=admin_headers
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_tree_with_inactive_orgs(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试包含已删除机构的树"""
# 创建机构
response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "INACTIVE_ORG",
"org_name": "未激活机构",
"org_type": "province"
}
)
org_id = response.json()["data"]["id"]
# 停用机构
await client.put(
f"/api/v1/organizations/{org_id}",
headers=admin_headers,
json={"status": "inactive"}
)
response = await client.get(
"/api/v1/organizations/tree",
headers=admin_headers
)
assert response.status_code == 200
# 验证树中是否包含未激活机构
@pytest.mark.asyncio
async def test_tree_sorting(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试树的排序"""
# 创建根机构
root_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "SORT_ROOT",
"org_name": "排序根",
"org_type": "province"
}
)
root_id = root_response.json()["data"]["id"]
# 创建不同排序的子机构
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "SECOND",
"org_name": "第二",
"org_type": "city",
"parent_id": root_id,
"sort_order": 2
}
)
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json={
"org_code": "FIRST",
"org_name": "第一",
"org_type": "city",
"parent_id": root_id,
"sort_order": 1
}
)
response = await client.get(
"/api/v1/organizations/tree",
headers=admin_headers
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_tree_depth_limit(
self,
client: AsyncClient,
admin_headers: dict
):
"""测试树的深度限制"""
response = await client.get(
"/api/v1/organizations/tree?max_depth=3",
headers=admin_headers
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_flatten_tree_to_list(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试将树展平为列表"""
response = await client.get(
"/api/v1/organizations?format=flat",
headers=admin_headers
)
assert response.status_code == 200
# ==================== 递归查询测试 ====================
class TestOrganizationRecursiveQuery:
"""测试机构递归查询"""
@pytest.mark.asyncio
async def test_get_all_children(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试获取所有子机构"""
# 创建多级机构
root_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "ROOT",
"org_name": "",
"org_type": "province"
})
)
root_id = root_response.json()["data"]["id"]
child1_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "CHILD1",
"org_name": "子1",
"org_type": "city",
"parent_id": root_id
})
)
child1_id = child1_response.json()["data"]["id"]
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "GRANDCHILD1",
"org_name": "孙1",
"org_type": "outlet",
"parent_id": child1_id
})
)
# 获取所有子机构
response = await client.get(
f"/api/v1/organizations/{root_id}/children",
headers=admin_headers
)
assert response.status_code == 200
# 应该包含所有子孙机构
@pytest.mark.asyncio
async def test_get_all_parents(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试获取所有父机构路径"""
# 创建三级机构
grand_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "GRAND",
"org_name": "祖父",
"org_type": "province"
})
)
grand_id = grand_response.json()["data"]["id"]
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "PARENT",
"org_name": "",
"org_type": "city",
"parent_id": grand_id
})
)
parent_id = parent_response.json()["data"]["id"]
child_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "CHILD_PATH",
"org_name": "",
"org_type": "outlet",
"parent_id": parent_id
})
)
child_id = child_response.json()["data"]["id"]
# 获取父机构路径
response = await client.get(
f"/api/v1/organizations/{child_id}/parents",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
# 应该包含完整的父机构路径
# assert len(data["data"]) == 2
@pytest.mark.asyncio
async def test_count_descendants(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试统计子孙机构数量"""
# 创建机构树
root_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "COUNT_ROOT",
"org_name": "统计根",
"org_type": "province"
})
)
root_id = root_response.json()["data"]["id"]
# 创建多个子机构
for i in range(3):
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": f"COUNT_CHILD{i}",
"org_name": f"{i}",
"org_type": "city",
"parent_id": root_id
})
)
response = await client.get(
f"/api/v1/organizations/{root_id}/descendants/count",
headers=admin_headers
)
assert response.status_code == 200
# data = response.json()
# assert data["data"]["count"] >= 3
@pytest.mark.asyncio
async def test_get_organization_level(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试获取机构层级"""
# 创建三级机构
root_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "LEVEL_ROOT",
"org_name": "层级根",
"org_type": "province"
})
)
root_id = root_response.json()["data"]["id"]
child_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "LEVEL_CHILD",
"org_name": "层级子",
"org_type": "city",
"parent_id": root_id
})
)
child_id = child_response.json()["data"]["id"]
grandchild_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "LEVEL_GRAND",
"org_name": "层级孙",
"org_type": "outlet",
"parent_id": child_id
})
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_get_siblings(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试获取兄弟机构"""
# 创建父机构
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "SIB_PARENT",
"org_name": "兄弟父",
"org_type": "province"
})
)
parent_id = parent_response.json()["data"]["id"]
# 创建多个子机构
for i in range(3):
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": f"SIB{i}",
"org_name": f"兄弟{i}",
"org_type": "city",
"parent_id": parent_id
})
)
response = await client.get(
f"/api/v1/organizations/{parent_id}/children",
headers=admin_headers
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_recursive_search(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试递归搜索机构"""
response = await client.get(
"/api/v1/organizations/search?keyword=测试&recursive=true",
headers=admin_headers
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_get_org_tree_statistics(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试获取机构树统计信息"""
response = await client.get(
"/api/v1/organizations/statistics",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
# 应该包含机构总数、层级数等统计信息
@pytest.mark.asyncio
async def test_recursive_delete_check(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试递归删除检查"""
# 创建有子机构的机构
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "DEL_CHECK",
"org_name": "删除检查",
"org_type": "province"
})
)
parent_id = parent_response.json()["data"]["id"]
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "DEL_CHILD",
"org_name": "删除子",
"org_type": "city",
"parent_id": parent_id
})
)
# 检查是否可以删除
response = await client.get(
f"/api/v1/organizations/{parent_id}/can-delete",
headers=admin_headers
)
assert response.status_code == 200
# data = response.json()
# assert data["data"]["can_delete"] is False
@pytest.mark.asyncio
async def test_get_org_full_path(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试获取机构完整路径"""
# 创建三级机构
grand_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "PATH_GRAND",
"org_name": "路径祖父",
"org_type": "province"
})
)
grand_id = grand_response.json()["data"]["id"]
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "PATH_PARENT",
"org_name": "路径父",
"org_type": "city",
"parent_id": grand_id
})
)
parent_id = parent_response.json()["data"]["id"]
child_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "PATH_CHILD",
"org_name": "路径子",
"org_type": "outlet",
"parent_id": parent_id
})
)
child_id = child_response.json()["data"]["id"]
# 获取完整路径
response = await client.get(
f"/api/v1/organizations/{child_id}/path",
headers=admin_headers
)
assert response.status_code == 200
data = response.json()
# 应该返回: 祖父 > 父 > 子
# ==================== 机构移动测试 ====================
class TestOrganizationMove:
"""测试机构移动"""
@pytest.mark.asyncio
async def test_move_organization_to_new_parent(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试移动机构到新的父机构"""
# 创建两个父机构
parent1_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "MOVE_PARENT1",
"org_name": "移动父1",
"org_type": "province"
})
)
parent1_id = parent1_response.json()["data"]["id"]
parent2_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "MOVE_PARENT2",
"org_name": "移动父2",
"org_type": "province"
})
)
parent2_id = parent2_response.json()["data"]["id"]
# 创建子机构
child_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "MOVE_CHILD",
"org_name": "移动子",
"org_type": "city",
"parent_id": parent1_id
})
)
child_id = child_response.json()["data"]["id"]
# 移动到新的父机构
response = await client.put(
f"/api/v1/organizations/{child_id}/move",
headers=admin_headers,
json({"new_parent_id": parent2_id})
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_move_organization_to_root(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试移动机构到根级别"""
# 创建父机构和子机构
parent_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "TO_ROOT_PARENT",
"org_name": "根父",
"org_type": "province"
})
)
parent_id = parent_response.json()["data"]["id"]
child_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "TO_ROOT_CHILD",
"org_name": "根子",
"org_type": "city",
"parent_id": parent_id
})
)
child_id = child_response.json()["data"]["id"]
# 移动到根级别
response = await client.put(
f"/api/v1/organizations/{child_id}/move",
headers=admin_headers,
json({"new_parent_id": None})
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_move_organization_with_children(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试移动有子机构的机构"""
# 创建机构树
root1_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "MOVE_ROOT1",
"org_name": "移动根1",
"org_type": "province"
})
)
root1_id = root1_response.json()["data"]["id"]
branch_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "MOVE_BRANCH",
"org_name": "移动分支",
"org_type": "city",
"parent_id": root1_id
})
)
branch_id = branch_response.json()["data"]["id"]
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "MOVE_LEAF",
"org_name": "移动叶子",
"org_type": "outlet",
"parent_id": branch_id
})
)
root2_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "MOVE_ROOT2",
"org_name": "移动根2",
"org_type": "province"
})
)
root2_id = root2_response.json()["data"]["id"]
# 移动分支(包括其子机构)
response = await client.put(
f"/api/v1/organizations/{branch_id}/move",
headers=admin_headers,
json({"new_parent_id": root2_id})
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_move_to_own_child_forbidden(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试禁止移动到自己的子机构"""
# 创建三级机构
root_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "CYCLE_ROOT",
"org_name": "循环根",
"org_type": "province"
})
)
root_id = root_response.json()["data"]["id"]
child_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "CYCLE_CHILD",
"org_name": "循环子",
"org_type": "city",
"parent_id": root_id
})
)
child_id = child_response.json()["data"]["id"]
# 尝试将根机构移动到子机构下(应该失败)
response = await client.put(
f"/api/v1/organizations/{root_id}/move",
headers=admin_headers,
json({"new_parent_id": child_id})
)
assert response.status_code in [400, 403]
@pytest.mark.asyncio
async def test_move_non_existent_org(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试移动不存在的机构"""
response = await client.put(
"/api/v1/organizations/999999/move",
headers=admin_headers,
json({"new_parent_id": None})
)
assert response.status_code == 404
# ==================== 并发测试 ====================
class TestOrganizationConcurrency:
"""测试机构并发操作"""
@pytest.mark.asyncio
async def test_concurrent_create_same_code(
self,
client: AsyncClient,
admin_headers: dict
):
"""测试并发创建相同代码的机构"""
import asyncio
data = {
"org_code": "CONCURRENT_ORG",
"org_name": "并发机构"
}
# 并发创建
tasks = [
client.post("/api/v1/organizations", headers=admin_headers, json=data)
for _ in range(2)
]
responses = await asyncio.gather(*tasks)
# 应该只有一个成功
success_count = sum(1 for r in responses if r.status_code == 200)
assert success_count == 1
@pytest.mark.asyncio
async def test_concurrent_update(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试并发更新机构"""
import asyncio
# 创建机构
create_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "CONCURRENT_UPDATE",
"org_name": "并发更新",
"org_type": "province"
})
)
org_id = create_response.json()["data"]["id"]
# 并发更新
tasks = [
client.put(
f"/api/v1/organizations/{org_id}",
headers=admin_headers,
json={"org_name": f"名称{i}"}
)
for i in range(5)
]
responses = await asyncio.gather(*tasks)
# 所有更新都应该成功
assert all(r.status_code == 200 for r in responses)
@pytest.mark.asyncio
async def test_concurrent_move_operations(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试并发移动操作"""
import asyncio
# 创建多个机构
parent_ids = []
for i in range(3):
response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": f"MOVE_PARENT{i}",
"org_name": f"移动父{i}",
"org_type": "province"
})
)
parent_ids.append(response.json()["data"]["id"])
child_response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "MOVE_TARGET",
"org_name": "移动目标",
"org_type": "city",
"parent_id": parent_ids[0]
})
)
child_id = child_response.json()["data"]["id"]
# 并发移动到不同的父机构
tasks = [
client.put(
f"/api/v1/organizations/{child_id}/move",
headers=admin_headers,
json({"new_parent_id": parent_id})
)
for parent_id in parent_ids
]
responses = await asyncio.gather(*tasks)
# 只有一个成功
success_count = sum(1 for r in responses if r.status_code == 200)
assert success_count >= 1
@pytest.mark.asyncio
async def test_concurrent_delete_and_move(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试并发删除和移动"""
import asyncio
# 创建机构
response = await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": "DELETE_MOVE_ORG",
"org_name": "删除移动",
"org_type": "province"
})
)
org_id = response.json()["data"]["id"]
# 并发删除和移动
delete_task = client.delete(
f"/api/v1/organizations/{org_id}",
headers=admin_headers
)
move_task = client.put(
f"/api/v1/organizations/{org_id}/move",
headers=admin_headers,
json({"new_parent_id": None})
)
responses = await asyncio.gather(delete_task, move_task)
# 至少一个操作失败
assert any(r.status_code != 200 for r in responses)
@pytest.mark.asyncio
async def test_concurrent_tree_queries(
self,
client: AsyncClient,
admin_headers: dict,
db_session
):
"""测试并发查询树结构"""
import asyncio
# 创建一些机构
for i in range(5):
await client.post(
"/api/v1/organizations",
headers=admin_headers,
json({
"org_code": f"QUERY_ORG{i}",
"org_name": f"查询机构{i}",
"org_type": "province"
})
)
# 并发查询树
tasks = [
client.get("/api/v1/organizations/tree", headers=admin_headers)
for _ in range(10)
]
responses = await asyncio.gather(*tasks)
# 所有查询都应该成功
assert all(r.status_code == 200 for r in responses)