Skip to content

Commit

Permalink
test: add organization user & relation api unittests (#1714)
Browse files Browse the repository at this point in the history
  • Loading branch information
narasux authored May 14, 2024
1 parent b816af7 commit ddc7c2f
Show file tree
Hide file tree
Showing 8 changed files with 911 additions and 67 deletions.
5 changes: 3 additions & 2 deletions src/bk-user/bkuser/apis/web/organization/serializers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def validate_leader_ids(self, leader_ids: List[str]) -> List[str]:
).values_list("id", flat=True)
)
if invalid_leader_ids:
raise ValidationError(_("指定的直属上级 {} 不存在").format(invalid_leader_ids))
raise ValidationError(_("指定的直属上级 {} 不存在").format(",".join(invalid_leader_ids)))

return leader_ids

Expand Down Expand Up @@ -286,7 +286,8 @@ def validate_extras(self, extras: Dict[str, Any]) -> Dict[str, Any]:
# 这里的处理策略是:在通过校验之后,用 DB 中的数据进行替换
exists_extras = DataSourceUser.objects.get(id=self.context["data_source_user_id"]).extras
for f in custom_fields.filter(manager_editable=False):
extras[f.name] = exists_extras[f.name]
if f.name in exists_extras:
extras[f.name] = exists_extras[f.name]

return extras

Expand Down
2 changes: 1 addition & 1 deletion src/bk-user/bkuser/apis/web/organization/views/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ class TenantUserBatchCreateApi(CurrentUserTenantDataSourceMixin, generics.Create
tags=["organization.user"],
operation_description="租户用户快速录入",
request_body=TenantUserBatchCreateInputSLZ(),
responses={status.HTTP_201_CREATED: ""},
responses={status.HTTP_204_NO_CONTENT: ""},
)
def post(self, request, *args, **kwargs):
cur_tenant_id = self.get_current_tenant_id()
Expand Down
102 changes: 99 additions & 3 deletions src/bk-user/tests/apis/web/organization/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,96 @@
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
from typing import List

import pytest
from bkuser.apps.data_source.constants import DataSourceTypeEnum
from bkuser.apps.data_source.models import DataSource
from bkuser.apps.tenant.constants import CollaborationStrategyStatus
from bkuser.apps.tenant.models import CollaborationStrategy, Tenant
from bkuser.apps.tenant.constants import CollaborationScopeType, CollaborationStrategyStatus, UserFieldDataType
from bkuser.apps.tenant.models import CollaborationStrategy, Tenant, TenantUserCustomField
from bkuser.plugins.local.models import LocalDataSourcePluginConfig

from tests.test_utils.data_source import init_data_source_users_depts_and_relations
from tests.test_utils.helpers import generate_random_string
from tests.test_utils.tenant import create_tenant, sync_users_depts_to_tenant


def _create_tenant_custom_fields(tenant: Tenant) -> List[TenantUserCustomField]:
"""
创建测试用的租户用户自定义字段
以租户 ID 为前缀,分别是 age(number), gender(enum), region(string)
"""
age_field = TenantUserCustomField.objects.create(
tenant=tenant,
name=f"{tenant.id}-age",
display_name="年龄",
data_type=UserFieldDataType.NUMBER,
required=True,
default=0,
)
gender_field = TenantUserCustomField.objects.create(
tenant=tenant,
name=f"{tenant.id}-gender",
display_name="性别",
data_type=UserFieldDataType.ENUM,
required=True,
default="male",
options=[
{"id": "male", "value": "男"},
{"id": "female", "value": "女"},
{"id": "other", "value": "其他"},
],
)
region_field = TenantUserCustomField.objects.create(
tenant=tenant,
name=f"{tenant.id}-region",
display_name="籍贯",
data_type=UserFieldDataType.STRING,
required=True,
default="china",
)
hobbies_field = TenantUserCustomField.objects.create(
tenant=tenant,
name=f"{tenant.id}-hobbies",
display_name="爱好",
data_type=UserFieldDataType.MULTI_ENUM,
required=True,
default=["music", "reading"],
options=[
{"id": "singing", "value": "唱歌"},
{"id": "shopping", "value": "购物"},
{"id": "reading", "value": "阅读"},
{"id": "dancing", "value": "跳舞"},
{"id": "gaming", "value": "游戏"},
{"id": "studying", "value": "学习"},
{"id": "driving", "value": "驾驶"},
{"id": "eating", "value": "吃饭"},
{"id": "collecting", "value": "采集"},
{"id": "sleeping", "value": "睡觉"},
{"id": "traveling", "value": "旅游"},
{"id": "hacking", "value": "骇入"},
{"id": "hunting", "value": "狩猎"},
{"id": "other", "value": "其他"},
],
)
return [age_field, gender_field, region_field, hobbies_field]


@pytest.fixture()
def random_tenant_custom_fields(random_tenant) -> List[TenantUserCustomField]:
"""随机租户的自定义字段"""
return _create_tenant_custom_fields(random_tenant)


@pytest.fixture()
def collaboration_tenant_custom_fields(collaboration_tenant) -> List[TenantUserCustomField]:
"""协同租户的自定义字段"""
return _create_tenant_custom_fields(collaboration_tenant)


@pytest.fixture()
def _init_tenant_users_depts(random_tenant, full_local_data_source) -> None:
def _init_tenant_users_depts(random_tenant, full_local_data_source, random_tenant_custom_fields) -> None:
"""初始化租户部门 & 租户用户"""
sync_users_depts_to_tenant(random_tenant, full_local_data_source)

Expand All @@ -36,6 +112,8 @@ def collaboration_tenant() -> Tenant:
def _init_collaboration_users_depts(
random_tenant,
collaboration_tenant,
random_tenant_custom_fields,
collaboration_tenant_custom_fields,
local_ds_plugin,
local_ds_plugin_cfg,
) -> None:
Expand All @@ -47,6 +125,24 @@ def _init_collaboration_users_depts(
target_tenant=random_tenant,
source_status=CollaborationStrategyStatus.ENABLED,
target_status=CollaborationStrategyStatus.ENABLED,
source_config={
"organization_scope_type": CollaborationScopeType.ALL,
"organization_scope_config": {},
"field_scope_type": CollaborationScopeType.ALL,
"field_scope_config": {},
},
target_config={
"organization_scope_type": CollaborationScopeType.ALL,
"organization_scope_config": {},
"field_mapping": [
{
"source_field": f"{collaboration_tenant.id}-{field}",
"mapping_operation": "direct",
"target_field": f"{random_tenant.id}-{field}",
}
for field in ["age", "gender", "region"]
],
},
)
data_source = DataSource.objects.create(
owner_tenant_id=collaboration_tenant.id,
Expand Down
22 changes: 22 additions & 0 deletions src/bk-user/tests/apis/web/organization/test_department.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,25 @@ def test_match_nothing(self, api_client):
resp = api_client.get(reverse("organization.tenant_department.search"), data={"keyword": "2887"})
assert resp.status_code == status.HTTP_200_OK
assert len(resp.data) == 0


class TestOptionalTenantDepartmentListApi:
@pytest.mark.usefixtures("_init_tenant_users_depts")
def test_search_dept(self, api_client, random_tenant):
resp = api_client.get(reverse("organization.optional_department.list"), data={"keyword": "部门"})

assert resp.status_code == status.HTTP_200_OK
assert {dept["name"] for dept in resp.data} == {"部门A", "部门B"}
assert {dept["organization_path"] for dept in resp.data} == {"公司/部门A", "公司/部门B"}

@pytest.mark.usefixtures("_init_tenant_users_depts")
def test_search_aa_keyword(self, api_client, random_tenant):
resp = api_client.get(reverse("organization.optional_department.list"), data={"keyword": "AA"})

assert resp.status_code == status.HTTP_200_OK
assert {dept["name"] for dept in resp.data} == {"中心AA", "小组AAA", "小组BAA"}
assert {dept["organization_path"] for dept in resp.data} == {
"公司/部门A/中心AA",
"公司/部门A/中心AA/小组AAA",
"公司/部门B/中心BA/小组BAA",
}
174 changes: 174 additions & 0 deletions src/bk-user/tests/apis/web/organization/test_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
import pytest
from bkuser.apps.data_source.models import (
DataSourceDepartment,
DataSourceDepartmentUserRelation,
)
from bkuser.apps.tenant.models import TenantDepartment, TenantUser
from django.urls import reverse
from django.utils.http import urlencode
from rest_framework import status

pytestmark = pytest.mark.django_db


class TestTenantDeptUserRelationBatchCreateApi:
"""测试 批量添加 / 拉取租户用户(添加部门 - 用户关系)"""

@pytest.mark.usefixtures("_init_tenant_users_depts")
def test_standard(self, api_client, random_tenant):
linshiyi = TenantUser.objects.get(data_source_user__username="linshiyi", tenant=random_tenant)
baishier = TenantUser.objects.get(data_source_user__username="baishier", tenant=random_tenant)
group_aaa = TenantDepartment.objects.get(data_source_department__name="小组AAA", tenant=random_tenant)
group_aba = TenantDepartment.objects.get(data_source_department__name="小组ABA", tenant=random_tenant)

resp = api_client.post(
reverse("organization.tenant_dept_user_relation.batch_create"),
data={
"user_ids": [linshiyi.id, baishier.id],
"target_department_ids": [group_aaa.id, group_aba.id],
},
)
assert resp.status_code == status.HTTP_204_NO_CONTENT

# baishier 属于小组 BAA,linshiyi 属于小组 ABA,将他们两个添加到 小组 AAA & 小组 ABA
# 则 baishier 应该属于三个组,linshiyi 属于两个组(因为原来就在 小组 ABA 中,会忽略冲突)
dept_ids = DataSourceDepartmentUserRelation.objects.filter(user_id=linshiyi.data_source_user_id).values_list(
"department_id", flat=True
)
dept_names = set(DataSourceDepartment.objects.filter(id__in=dept_ids).values_list("name", flat=True))
assert dept_names == {"小组AAA", "小组ABA"}

dept_ids = DataSourceDepartmentUserRelation.objects.filter(user_id=baishier.data_source_user_id).values_list(
"department_id", flat=True
)
dept_names = set(DataSourceDepartment.objects.filter(id__in=dept_ids).values_list("name", flat=True))
assert dept_names == {"小组AAA", "小组ABA", "小组BAA"}

@pytest.mark.usefixtures("_init_tenant_users_depts")
def test_with_invalid_user_id(self, api_client, random_tenant):
company = TenantDepartment.objects.get(data_source_department__name="公司", tenant=random_tenant)

resp = api_client.post(
reverse("organization.tenant_dept_user_relation.batch_create"),
data={"user_ids": ["not_exists"], "target_department_ids": [company.id]},
)

assert resp.status_code == status.HTTP_400_BAD_REQUEST
assert "用户 ID not_exists 在当前租户中不存在" in resp.data["message"]

@pytest.mark.usefixtures("_init_tenant_users_depts")
def test_with_invalid_department_id(self, api_client, random_tenant):
zhangsan = TenantUser.objects.get(data_source_user__username="zhangsan", tenant=random_tenant)

resp = api_client.post(
reverse("organization.tenant_dept_user_relation.batch_create"),
data={"user_ids": [zhangsan.id], "target_department_ids": [-1]},
)

assert resp.status_code == status.HTTP_400_BAD_REQUEST
assert "部门 ID {-1} 在当前租户中不存在" in resp.data["message"]


class TestTenantDeptUserRelationBatchUpdatePutApi:
"""测试 清空并加入到其他组织(会删除当前所有关系)"""

@pytest.mark.usefixtures("_init_tenant_users_depts")
def test_standard(self, api_client, random_tenant):
wangwu = TenantUser.objects.get(data_source_user__username="wangwu", tenant=random_tenant)
liuqi = TenantUser.objects.get(data_source_user__username="liuqi", tenant=random_tenant)
dept_b = TenantDepartment.objects.get(data_source_department__name="部门B", tenant=random_tenant)
center_ab = TenantDepartment.objects.get(data_source_department__name="中心AB", tenant=random_tenant)

resp = api_client.put(
reverse("organization.tenant_dept_user_relation.batch_update"),
data={
"user_ids": [wangwu.id, liuqi.id],
"target_department_ids": [dept_b.id, center_ab.id],
},
)
assert resp.status_code == status.HTTP_204_NO_CONTENT

# wangwu 属于部门 A & 部门 B,linshiyi 属于小组 AAA,将他们清空并加入
# 部门 B & 中心 AB,则 wangwu,liuqi 应该只属于部门 B & 中心 AB
for user in [wangwu, liuqi]:
relations = DataSourceDepartmentUserRelation.objects.filter(user_id=user.data_source_user_id)
assert set(relations.values_list("department_id", flat=True)) == {
dept_b.data_source_department_id,
center_ab.data_source_department_id,
}


class TestTenantDeptUserRelationBatchUpdatePatchApi:
"""测试 移至其他组织(仅删除当前部门关系)"""

@pytest.mark.usefixtures("_init_tenant_users_depts")
def test_standard(self, api_client, random_tenant):
lushi = TenantUser.objects.get(data_source_user__username="lushi", tenant=random_tenant)
linshiyi = TenantUser.objects.get(data_source_user__username="linshiyi", tenant=random_tenant)
group_aaa = TenantDepartment.objects.get(data_source_department__name="小组AAA", tenant=random_tenant)
group_aba = TenantDepartment.objects.get(data_source_department__name="小组ABA", tenant=random_tenant)
group_baa = TenantDepartment.objects.get(data_source_department__name="小组BAA", tenant=random_tenant)

resp = api_client.patch(
reverse("organization.tenant_dept_user_relation.batch_update"),
data={
"user_ids": [lushi.id, linshiyi.id],
"target_department_ids": [group_aaa.id, group_baa.id],
"source_department_id": group_aba.id,
},
)
assert resp.status_code == status.HTTP_204_NO_CONTENT

# lushi 属中心 BA,小组 ABA,linshiyi 只属于小组 ABA,将他们退出小组 ABA 并加入小组 AAA & 小组 BAA
# 则 lushi 应该属于中心 BA & 小组 AAA & 小组 BAA,linshiyi 应该属于小组 AAA & 小组 BAA
dept_ids = DataSourceDepartmentUserRelation.objects.filter(user_id=lushi.data_source_user_id).values_list(
"department_id", flat=True
)
dept_names = set(DataSourceDepartment.objects.filter(id__in=dept_ids).values_list("name", flat=True))
assert dept_names == {"中心BA", "小组AAA", "小组BAA"}

dept_ids = DataSourceDepartmentUserRelation.objects.filter(user_id=linshiyi.data_source_user_id).values_list(
"department_id", flat=True
)
dept_names = set(DataSourceDepartment.objects.filter(id__in=dept_ids).values_list("name", flat=True))
assert dept_names == {"小组AAA", "小组BAA"}


class TestTenantDeptUserRelationBatchDeleteApi:
"""测试 移出当前组织(仅删除当前部门关系)"""

@pytest.mark.usefixtures("_init_tenant_users_depts")
def test_standard(self, api_client, random_tenant):
lisi = TenantUser.objects.get(data_source_user__username="lisi", tenant=random_tenant)
zhaoliu = TenantUser.objects.get(data_source_user__username="zhaoliu", tenant=random_tenant)
dept_a = TenantDepartment.objects.get(data_source_department__name="部门A", tenant=random_tenant)
center_aa = TenantDepartment.objects.get(data_source_department__name="中心AA", tenant=random_tenant)

resp = api_client.delete(
reverse("organization.tenant_dept_user_relation.batch_delete"),
QUERY_STRING=urlencode(
{
"user_ids": ",".join([lisi.id, zhaoliu.id]),
"source_department_id": center_aa.id,
},
doseq=True,
),
)
assert resp.status_code == status.HTTP_204_NO_CONTENT

# lisi,zhaoliu 属于中心 AA,将他们移出中心 AA 后,zhaoliu 不属于任何部门,lisi 还属于部门 A
relations = DataSourceDepartmentUserRelation.objects.filter(user_id=lisi.data_source_user_id)
assert relations.count() == 1
assert relations.first().department_id == dept_a.data_source_department_id

assert not DataSourceDepartmentUserRelation.objects.filter(user_id=zhaoliu.data_source_user_id).exists()
Loading

0 comments on commit ddc7c2f

Please # to comment.