1548 lines
43 KiB
Python
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)
|