fiuai-sdk-python 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/PKG-INFO +2 -1
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/pyproject.toml +2 -1
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/__init__.py +0 -2
- fiuai_sdk_python-0.3.2/src/fiuai_sdk_python/auth/__init__.py +25 -0
- fiuai_sdk_python-0.3.2/src/fiuai_sdk_python/auth/header.py +90 -0
- fiuai_sdk_python-0.3.2/src/fiuai_sdk_python/auth/helper.py +161 -0
- fiuai_sdk_python-0.3.2/src/fiuai_sdk_python/auth/test_auth.py +273 -0
- fiuai_sdk_python-0.3.2/src/fiuai_sdk_python/auth/type.py +31 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/bank.py +6 -6
- fiuai_sdk_python-0.3.2/src/fiuai_sdk_python/client.py +263 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/company.py +3 -3
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/setup.py +4 -4
- fiuai_sdk_python-0.3.2/src/fiuai_sdk_python/test_all.py +73 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/type.py +0 -3
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/util.py +6 -17
- fiuai_sdk_python-0.3.0/src/fiuai_sdk_python/client.py +0 -416
- fiuai_sdk_python-0.3.0/src/fiuai_sdk_python/token.py +0 -84
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/.gitignore +0 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/CHANGELOG.md +0 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/LICENSE +0 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/README.md +0 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/const.py +0 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/error.py +0 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/item.py +0 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/perm.py +0 -0
- {fiuai_sdk_python-0.3.0 → fiuai_sdk_python-0.3.2}/src/fiuai_sdk_python/profile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fiuai_sdk_python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: FiuAI Python SDK - 企业级AI服务集成开发工具包
|
|
5
5
|
Project-URL: Homepage, https://github.com/fiuai/fiuai-sdk-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/fiuai/fiuai-sdk-python#readme
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
23
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
24
|
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: fastapi>=0.118.2
|
|
25
26
|
Requires-Dist: httpx>=0.28.1
|
|
26
27
|
Requires-Dist: pydantic>=2.11.7
|
|
27
28
|
Requires-Dist: pytest>=8.4.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fiuai_sdk_python"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "FiuAI Python SDK - 企业级AI服务集成开发工具包"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -24,6 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
+
"fastapi>=0.118.2",
|
|
27
28
|
"httpx>=0.28.1",
|
|
28
29
|
"pydantic>=2.11.7",
|
|
29
30
|
"pytest>=8.4.1",
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
from .client import FiuaiSDK, get_client
|
|
2
|
-
from .token import TokenConfig
|
|
3
2
|
from .util import init_fiuai
|
|
4
3
|
from .profile import UserProfileInfo
|
|
5
4
|
from .type import UserProfile
|
|
6
5
|
|
|
7
6
|
__all__ = [
|
|
8
7
|
'FiuaiSDK',
|
|
9
|
-
'TokenConfig',
|
|
10
8
|
'init_fiuai',
|
|
11
9
|
'get_client',
|
|
12
10
|
'UserProfileInfo',
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .header import parse_auth_headers, extract_auth_from_request
|
|
2
|
+
from .type import AuthData, AuthHeader
|
|
3
|
+
from .helper import (
|
|
4
|
+
get_auth_data,
|
|
5
|
+
get_current_user_id,
|
|
6
|
+
get_current_tenant_id,
|
|
7
|
+
get_current_company,
|
|
8
|
+
get_company_unique_no,
|
|
9
|
+
get_impersonation,
|
|
10
|
+
is_impersonating
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"parse_auth_headers",
|
|
15
|
+
"extract_auth_from_request",
|
|
16
|
+
"AuthData",
|
|
17
|
+
"AuthHeader",
|
|
18
|
+
"get_auth_data",
|
|
19
|
+
"get_current_user_id",
|
|
20
|
+
"get_current_tenant_id",
|
|
21
|
+
"get_current_company",
|
|
22
|
+
"get_company_unique_no",
|
|
23
|
+
"get_impersonation",
|
|
24
|
+
"is_impersonating",
|
|
25
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# -- coding: utf-8 --
|
|
2
|
+
# Project: auth
|
|
3
|
+
# Created Date: 2025-01-09
|
|
4
|
+
# Author: liming
|
|
5
|
+
# Email: lmlala@aliyun.com
|
|
6
|
+
# Copyright (c) 2025 FiuAI
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Optional, Union, Literal
|
|
9
|
+
from .type import AuthData
|
|
10
|
+
from fastapi import Request
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
|
|
14
|
+
"""
|
|
15
|
+
从 HTTP headers 中解析认证信息并转换为 AuthData
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
headers: HTTP headers 字典
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
AuthData 对象,如果解析失败则返回 None
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: 当必需的头信息缺失或格式错误时
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
# 获取必需的头信息(注意:HTTP headers 会自动转换为小写)
|
|
28
|
+
user_id = headers.get("x-fiuai-user")
|
|
29
|
+
auth_tenant_id = headers.get("x-fiuai-auth-tenant-id")
|
|
30
|
+
current_company_str = headers.get("x-fiuai-current-company", "")
|
|
31
|
+
impersonation = headers.get("x-fiuai-impersonation", "")
|
|
32
|
+
|
|
33
|
+
# 验证必需字段
|
|
34
|
+
if not user_id:
|
|
35
|
+
raise ValueError("Missing required header: x-fiuai-user")
|
|
36
|
+
if not auth_tenant_id:
|
|
37
|
+
raise ValueError("Missing required header: x-fiuai-auth-tenant-id")
|
|
38
|
+
|
|
39
|
+
# 解析当前公司列表(逗号分隔)
|
|
40
|
+
current_company = []
|
|
41
|
+
if current_company_str:
|
|
42
|
+
current_company = [company.strip() for company in current_company_str.split(",") if company.strip()]
|
|
43
|
+
|
|
44
|
+
# 检查是否有有效的公司信息
|
|
45
|
+
if not current_company:
|
|
46
|
+
raise ValueError("Missing required header: x-fiuai-current-company")
|
|
47
|
+
|
|
48
|
+
return AuthData(
|
|
49
|
+
user_id=user_id,
|
|
50
|
+
auth_tenant_id=auth_tenant_id,
|
|
51
|
+
current_company=current_company[0],
|
|
52
|
+
impersonation=impersonation,
|
|
53
|
+
company_unique_no=current_company[0]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
# 记录错误但不抛出异常,返回 None 表示解析失败
|
|
58
|
+
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def extract_auth_from_request(
|
|
63
|
+
request: Union[Request, Dict[str, str]],
|
|
64
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
65
|
+
) -> Optional[AuthData]:
|
|
66
|
+
"""
|
|
67
|
+
从请求对象中提取认证信息
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
request: FastAPI Request 对象或原生 headers 字典
|
|
71
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
AuthData 对象,如果解析失败则返回 None, 或抛出异常
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
TypeError: 当 request 类型与 engine 不匹配时
|
|
78
|
+
"""
|
|
79
|
+
if engine == "fastapi":
|
|
80
|
+
if not isinstance(request, Request):
|
|
81
|
+
raise TypeError("request must be a FastAPI Request object when engine='fastapi'")
|
|
82
|
+
headers = dict(request.headers)
|
|
83
|
+
elif engine == "dict":
|
|
84
|
+
if not isinstance(request, dict):
|
|
85
|
+
raise TypeError("request must be a dict when engine='dict'")
|
|
86
|
+
headers = request
|
|
87
|
+
else:
|
|
88
|
+
raise ValueError("engine must be either 'fastapi' or 'dict'")
|
|
89
|
+
|
|
90
|
+
return parse_auth_headers(headers)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# -- coding: utf-8 --
|
|
2
|
+
# Project: auth
|
|
3
|
+
# Created Date: 2025-01-09
|
|
4
|
+
# Author: liming
|
|
5
|
+
# Email: lmlala@aliyun.com
|
|
6
|
+
# Copyright (c) 2025 FiuAI
|
|
7
|
+
|
|
8
|
+
from fastapi import Request, HTTPException
|
|
9
|
+
from typing import Optional, Union, Dict, Literal
|
|
10
|
+
from .type import AuthData
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_auth_data(
|
|
14
|
+
request: Union[Request, Dict[str, str]],
|
|
15
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
16
|
+
) -> AuthData:
|
|
17
|
+
"""
|
|
18
|
+
从请求中获取认证数据
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
request: FastAPI Request 对象或包含认证数据的字典
|
|
22
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
AuthData: 认证数据
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
HTTPException: 当认证数据不存在时抛出 401 错误(仅限 FastAPI)
|
|
29
|
+
ValueError: 当使用 dict 引擎且认证数据不存在时
|
|
30
|
+
"""
|
|
31
|
+
if engine == "fastapi":
|
|
32
|
+
if not isinstance(request, Request):
|
|
33
|
+
raise TypeError("request must be a FastAPI Request object when engine='fastapi'")
|
|
34
|
+
auth_data = getattr(request.state, 'auth_data', None)
|
|
35
|
+
if not auth_data:
|
|
36
|
+
raise HTTPException(
|
|
37
|
+
status_code=401,
|
|
38
|
+
detail={
|
|
39
|
+
"error": "Unauthorized",
|
|
40
|
+
"message": "Authentication data not found",
|
|
41
|
+
"code": "AUTH_NOT_FOUND"
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
return auth_data
|
|
45
|
+
elif engine == "dict":
|
|
46
|
+
if not isinstance(request, dict):
|
|
47
|
+
raise TypeError("request must be a dict when engine='dict'")
|
|
48
|
+
auth_data = request.get('auth_data')
|
|
49
|
+
if not auth_data:
|
|
50
|
+
raise ValueError("Authentication data not found in request dict")
|
|
51
|
+
return auth_data
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError("engine must be either 'fastapi' or 'dict'")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_current_user_id(
|
|
57
|
+
request: Union[Request, Dict[str, str]],
|
|
58
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
59
|
+
) -> str:
|
|
60
|
+
"""
|
|
61
|
+
获取当前用户ID
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
request: FastAPI Request 对象或包含认证数据的字典
|
|
65
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: 用户ID
|
|
69
|
+
"""
|
|
70
|
+
auth_data = get_auth_data(request, engine)
|
|
71
|
+
return auth_data.user_id
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_current_tenant_id(
|
|
75
|
+
request: Union[Request, Dict[str, str]],
|
|
76
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
77
|
+
) -> str:
|
|
78
|
+
"""
|
|
79
|
+
获取当前租户ID
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
request: FastAPI Request 对象或包含认证数据的字典
|
|
83
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
str: 租户ID
|
|
87
|
+
"""
|
|
88
|
+
auth_data = get_auth_data(request, engine)
|
|
89
|
+
return auth_data.auth_tenant_id
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_current_company(
|
|
93
|
+
request: Union[Request, Dict[str, str]],
|
|
94
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
95
|
+
) -> str:
|
|
96
|
+
"""
|
|
97
|
+
获取当前公司ID
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
request: FastAPI Request 对象或包含认证数据的字典
|
|
101
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
str: 公司ID
|
|
105
|
+
"""
|
|
106
|
+
auth_data = get_auth_data(request, engine)
|
|
107
|
+
return auth_data.current_company
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_company_unique_no(
|
|
111
|
+
request: Union[Request, Dict[str, str]],
|
|
112
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
113
|
+
) -> str:
|
|
114
|
+
"""
|
|
115
|
+
获取当前公司唯一编号
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
request: FastAPI Request 对象或包含认证数据的字典
|
|
119
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
str: 公司唯一编号
|
|
123
|
+
"""
|
|
124
|
+
auth_data = get_auth_data(request, engine)
|
|
125
|
+
return auth_data.company_unique_no
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_impersonation(
|
|
129
|
+
request: Union[Request, Dict[str, str]],
|
|
130
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
131
|
+
) -> str:
|
|
132
|
+
"""
|
|
133
|
+
获取当前代表的租户ID
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
request: FastAPI Request 对象或包含认证数据的字典
|
|
137
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
str: 代表的租户ID
|
|
141
|
+
"""
|
|
142
|
+
auth_data = get_auth_data(request, engine)
|
|
143
|
+
return auth_data.impersonation
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def is_impersonating(
|
|
147
|
+
request: Union[Request, Dict[str, str]],
|
|
148
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
149
|
+
) -> bool:
|
|
150
|
+
"""
|
|
151
|
+
检查是否正在代表其他租户
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
request: FastAPI Request 对象或包含认证数据的字典
|
|
155
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
bool: 是否正在代表其他租户
|
|
159
|
+
"""
|
|
160
|
+
auth_data = get_auth_data(request, engine)
|
|
161
|
+
return bool(auth_data.impersonation and auth_data.impersonation != auth_data.auth_tenant_id)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# -- coding: utf-8 --
|
|
2
|
+
# Project: auth
|
|
3
|
+
# Created Date: 2025-01-09
|
|
4
|
+
# Author: liming
|
|
5
|
+
# Email: lmlala@aliyun.com
|
|
6
|
+
# Copyright (c) 2025 FiuAI
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
认证功能测试
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
15
|
+
|
|
16
|
+
from .header import parse_auth_headers, extract_auth_from_request
|
|
17
|
+
from .helper import (
|
|
18
|
+
get_auth_data,
|
|
19
|
+
get_current_user_id,
|
|
20
|
+
get_current_tenant_id,
|
|
21
|
+
get_current_company,
|
|
22
|
+
get_company_unique_no,
|
|
23
|
+
get_impersonation,
|
|
24
|
+
is_impersonating
|
|
25
|
+
)
|
|
26
|
+
from .type import AuthData
|
|
27
|
+
from unittest.mock import Mock
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_parse_auth_headers_success():
|
|
31
|
+
"""测试成功解析认证头信息"""
|
|
32
|
+
headers = {
|
|
33
|
+
"x-fiuai-user": "user123",
|
|
34
|
+
"x-fiuai-auth-tenant-id": "tenant456",
|
|
35
|
+
"x-fiuai-current-company": "company1,company2",
|
|
36
|
+
"x-fiuai-impersonation": "imp_tenant789"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
auth_data = parse_auth_headers(headers)
|
|
40
|
+
|
|
41
|
+
assert auth_data is not None
|
|
42
|
+
assert auth_data.user_id == "user123"
|
|
43
|
+
assert auth_data.auth_tenant_id == "tenant456"
|
|
44
|
+
assert auth_data.current_company == "company1"
|
|
45
|
+
assert auth_data.company_unique_no == "company1"
|
|
46
|
+
assert auth_data.impersonation == "imp_tenant789"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_parse_auth_headers_missing_required():
|
|
50
|
+
"""测试缺少必需头信息的情况"""
|
|
51
|
+
headers = {
|
|
52
|
+
"x-fiuai-user": "user123",
|
|
53
|
+
# 缺少 x-fiuai-auth-tenant-id
|
|
54
|
+
"x-fiuai-current-company": "company1",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
auth_data = parse_auth_headers(headers)
|
|
59
|
+
assert False, "应该抛出 ValueError"
|
|
60
|
+
except ValueError as e:
|
|
61
|
+
assert "Missing required header: x-fiuai-auth-tenant-id" in str(e)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_parse_auth_headers_empty_company():
|
|
65
|
+
"""测试空公司列表的情况"""
|
|
66
|
+
headers = {
|
|
67
|
+
"x-fiuai-user": "user123",
|
|
68
|
+
"x-fiuai-auth-tenant-id": "tenant456",
|
|
69
|
+
"x-fiuai-current-company": "", # 空字符串
|
|
70
|
+
"x-fiuai-impersonation": ""
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
auth_data = parse_auth_headers(headers)
|
|
75
|
+
assert False, "应该抛出 ValueError"
|
|
76
|
+
except ValueError as e:
|
|
77
|
+
assert "Missing required header: x-fiuai-current-company" in str(e)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_parse_auth_headers_multiple_companies():
|
|
81
|
+
"""测试多个公司的情况"""
|
|
82
|
+
headers = {
|
|
83
|
+
"x-fiuai-user": "user123",
|
|
84
|
+
"x-fiuai-auth-tenant-id": "tenant456",
|
|
85
|
+
"x-fiuai-current-company": "company1,company2,company3",
|
|
86
|
+
"x-fiuai-impersonation": ""
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
auth_data = parse_auth_headers(headers)
|
|
90
|
+
|
|
91
|
+
assert auth_data is not None
|
|
92
|
+
assert auth_data.current_company == "company1" # 取第一个
|
|
93
|
+
assert auth_data.company_unique_no == "company1"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_parse_auth_headers_with_spaces():
|
|
97
|
+
"""测试包含空格的公司列表"""
|
|
98
|
+
headers = {
|
|
99
|
+
"x-fiuai-user": "user123",
|
|
100
|
+
"x-fiuai-auth-tenant-id": "tenant456",
|
|
101
|
+
"x-fiuai-current-company": " company1 , company2 , company3 ",
|
|
102
|
+
"x-fiuai-impersonation": ""
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
auth_data = parse_auth_headers(headers)
|
|
106
|
+
|
|
107
|
+
assert auth_data is not None
|
|
108
|
+
assert auth_data.current_company == "company1" # 取第一个,已去除空格
|
|
109
|
+
assert auth_data.company_unique_no == "company1"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_extract_auth_from_request_fastapi():
|
|
113
|
+
"""测试从 FastAPI Request 对象提取认证信息"""
|
|
114
|
+
# 模拟 FastAPI Request 对象
|
|
115
|
+
from fastapi import Request
|
|
116
|
+
mock_request = Mock(spec=Request)
|
|
117
|
+
mock_request.headers = {
|
|
118
|
+
"x-fiuai-user": "user123",
|
|
119
|
+
"x-fiuai-auth-tenant-id": "tenant456",
|
|
120
|
+
"x-fiuai-current-company": "company1,company2",
|
|
121
|
+
"x-fiuai-impersonation": "imp_tenant789"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
auth_data = extract_auth_from_request(mock_request, engine="fastapi")
|
|
125
|
+
|
|
126
|
+
assert auth_data is not None
|
|
127
|
+
assert auth_data.user_id == "user123"
|
|
128
|
+
assert auth_data.auth_tenant_id == "tenant456"
|
|
129
|
+
assert auth_data.current_company == "company1"
|
|
130
|
+
assert auth_data.company_unique_no == "company1"
|
|
131
|
+
assert auth_data.impersonation == "imp_tenant789"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_extract_auth_from_request_dict():
|
|
135
|
+
"""测试从原生字典提取认证信息"""
|
|
136
|
+
headers_dict = {
|
|
137
|
+
"x-fiuai-user": "user123",
|
|
138
|
+
"x-fiuai-auth-tenant-id": "tenant456",
|
|
139
|
+
"x-fiuai-current-company": "company1,company2",
|
|
140
|
+
"x-fiuai-impersonation": "imp_tenant789"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
auth_data = extract_auth_from_request(headers_dict, engine="dict")
|
|
144
|
+
|
|
145
|
+
assert auth_data is not None
|
|
146
|
+
assert auth_data.user_id == "user123"
|
|
147
|
+
assert auth_data.auth_tenant_id == "tenant456"
|
|
148
|
+
assert auth_data.current_company == "company1"
|
|
149
|
+
assert auth_data.company_unique_no == "company1"
|
|
150
|
+
assert auth_data.impersonation == "imp_tenant789"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_extract_auth_from_request_wrong_type():
|
|
154
|
+
"""测试类型错误的情况"""
|
|
155
|
+
# 测试 FastAPI 引擎但传入字典
|
|
156
|
+
try:
|
|
157
|
+
extract_auth_from_request({"test": "value"}, engine="fastapi")
|
|
158
|
+
assert False, "应该抛出 TypeError"
|
|
159
|
+
except TypeError as e:
|
|
160
|
+
assert "request must be a FastAPI Request object" in str(e)
|
|
161
|
+
|
|
162
|
+
# 测试 dict 引擎但传入 Request 对象
|
|
163
|
+
from fastapi import Request
|
|
164
|
+
mock_request = Mock(spec=Request)
|
|
165
|
+
try:
|
|
166
|
+
extract_auth_from_request(mock_request, engine="dict")
|
|
167
|
+
assert False, "应该抛出 TypeError"
|
|
168
|
+
except TypeError as e:
|
|
169
|
+
assert "request must be a dict" in str(e)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_helper_functions_fastapi():
|
|
173
|
+
"""测试 FastAPI 模式下的辅助函数"""
|
|
174
|
+
# 模拟 FastAPI Request 对象,包含认证数据
|
|
175
|
+
from fastapi import Request
|
|
176
|
+
mock_request = Mock(spec=Request)
|
|
177
|
+
auth_data = AuthData(
|
|
178
|
+
user_id="user123",
|
|
179
|
+
auth_tenant_id="tenant456",
|
|
180
|
+
current_company="company1",
|
|
181
|
+
impersonation="imp_tenant789",
|
|
182
|
+
company_unique_no="company1"
|
|
183
|
+
)
|
|
184
|
+
mock_request.state.auth_data = auth_data
|
|
185
|
+
|
|
186
|
+
# 测试各种辅助函数
|
|
187
|
+
assert get_current_user_id(mock_request, engine="fastapi") == "user123"
|
|
188
|
+
assert get_current_tenant_id(mock_request, engine="fastapi") == "tenant456"
|
|
189
|
+
assert get_current_company(mock_request, engine="fastapi") == "company1"
|
|
190
|
+
assert get_company_unique_no(mock_request, engine="fastapi") == "company1"
|
|
191
|
+
assert get_impersonation(mock_request, engine="fastapi") == "imp_tenant789"
|
|
192
|
+
assert is_impersonating(mock_request, engine="fastapi") == True
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_helper_functions_dict():
|
|
196
|
+
"""测试 dict 模式下的辅助函数"""
|
|
197
|
+
# 模拟包含认证数据的字典
|
|
198
|
+
auth_data = AuthData(
|
|
199
|
+
user_id="user123",
|
|
200
|
+
auth_tenant_id="tenant456",
|
|
201
|
+
current_company="company1",
|
|
202
|
+
impersonation="imp_tenant789",
|
|
203
|
+
company_unique_no="company1"
|
|
204
|
+
)
|
|
205
|
+
request_dict = {"auth_data": auth_data}
|
|
206
|
+
|
|
207
|
+
# 测试各种辅助函数
|
|
208
|
+
assert get_current_user_id(request_dict, engine="dict") == "user123"
|
|
209
|
+
assert get_current_tenant_id(request_dict, engine="dict") == "tenant456"
|
|
210
|
+
assert get_current_company(request_dict, engine="dict") == "company1"
|
|
211
|
+
assert get_company_unique_no(request_dict, engine="dict") == "company1"
|
|
212
|
+
assert get_impersonation(request_dict, engine="dict") == "imp_tenant789"
|
|
213
|
+
assert is_impersonating(request_dict, engine="dict") == True
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_helper_functions_missing_auth_data():
|
|
217
|
+
"""测试缺少认证数据的情况"""
|
|
218
|
+
# 测试 FastAPI 模式
|
|
219
|
+
from fastapi import Request
|
|
220
|
+
mock_request = Mock(spec=Request)
|
|
221
|
+
mock_request.state.auth_data = None
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
get_auth_data(mock_request, engine="fastapi")
|
|
225
|
+
assert False, "应该抛出 HTTPException"
|
|
226
|
+
except Exception as e:
|
|
227
|
+
assert "Authentication data not found" in str(e)
|
|
228
|
+
|
|
229
|
+
# 测试 dict 模式
|
|
230
|
+
request_dict = {}
|
|
231
|
+
try:
|
|
232
|
+
get_auth_data(request_dict, engine="dict")
|
|
233
|
+
assert False, "应该抛出 ValueError"
|
|
234
|
+
except ValueError as e:
|
|
235
|
+
assert "Authentication data not found" in str(e)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_is_impersonating_false():
|
|
239
|
+
"""测试非代表模式的情况"""
|
|
240
|
+
auth_data = AuthData(
|
|
241
|
+
user_id="user123",
|
|
242
|
+
auth_tenant_id="tenant456",
|
|
243
|
+
current_company="company1",
|
|
244
|
+
impersonation="", # 空字符串
|
|
245
|
+
company_unique_no="company1"
|
|
246
|
+
)
|
|
247
|
+
request_dict = {"auth_data": auth_data}
|
|
248
|
+
|
|
249
|
+
assert is_impersonating(request_dict, engine="dict") == False
|
|
250
|
+
|
|
251
|
+
# 测试 impersonation 等于 auth_tenant_id 的情况
|
|
252
|
+
auth_data.impersonation = "tenant456"
|
|
253
|
+
assert is_impersonating(request_dict, engine="dict") == False
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
# 运行原有测试
|
|
258
|
+
test_parse_auth_headers_success()
|
|
259
|
+
test_parse_auth_headers_missing_required()
|
|
260
|
+
test_parse_auth_headers_empty_company()
|
|
261
|
+
test_parse_auth_headers_multiple_companies()
|
|
262
|
+
test_parse_auth_headers_with_spaces()
|
|
263
|
+
|
|
264
|
+
# 运行新增的测试
|
|
265
|
+
test_extract_auth_from_request_fastapi()
|
|
266
|
+
test_extract_auth_from_request_dict()
|
|
267
|
+
test_extract_auth_from_request_wrong_type()
|
|
268
|
+
test_helper_functions_fastapi()
|
|
269
|
+
test_helper_functions_dict()
|
|
270
|
+
test_helper_functions_missing_auth_data()
|
|
271
|
+
test_is_impersonating_false()
|
|
272
|
+
|
|
273
|
+
print("所有测试通过!")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -- coding: utf-8 --
|
|
2
|
+
# Project: auth
|
|
3
|
+
# Created Date: 2025 09 Th
|
|
4
|
+
# Author: liming
|
|
5
|
+
# Email: lmlala@aliyun.com
|
|
6
|
+
# Copyright (c) 2025 FiuAI
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
class AuthData(BaseModel):
|
|
13
|
+
user_id: str = Field(description="用户ID")
|
|
14
|
+
auth_tenant_id: str = Field(description="租户ID")
|
|
15
|
+
current_company: str = Field(description="当前公司ID列表")
|
|
16
|
+
impersonation: str = Field(description="当前代表的租户ID", default="")
|
|
17
|
+
company_unique_no: str = Field(description="当前公司唯一编号,正常情况等于current_company")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# edoc
|
|
22
|
+
|
|
23
|
+
class AuthHeader(BaseModel):
|
|
24
|
+
"""HTTP 认证头信息模型"""
|
|
25
|
+
x_fiuai_user: str = Field(alias="x-fiuai-user", description="用户ID")
|
|
26
|
+
x_fiuai_auth_tenant_id: str = Field(alias="x-fiuai-auth-tenant-id", description="租户ID")
|
|
27
|
+
x_fiuai_current_company: str = Field(alias="x-fiuai-current-company", default="", description="当前公司ID列表(逗号分隔)")
|
|
28
|
+
x_fiuai_impersonation: str = Field(alias="x-fiuai-impersonation", default="", description="当前代表的租户ID")
|
|
29
|
+
|
|
30
|
+
class Config:
|
|
31
|
+
populate_by_name = True
|
|
@@ -46,7 +46,7 @@ def get_bank_account(client: FiuaiSDK, bank_account_no: str) -> Optional[Company
|
|
|
46
46
|
logger.error(f"bank_account_no is empty, return None")
|
|
47
47
|
return None
|
|
48
48
|
|
|
49
|
-
_bank_account = client.
|
|
49
|
+
_bank_account = client.internal_get(
|
|
50
50
|
doctype="Bank Account",
|
|
51
51
|
name=bank_account_no,
|
|
52
52
|
fields=["name","bank_account_no", "account_name", "bank", "opening_bank_branch"]
|
|
@@ -58,7 +58,7 @@ def get_bank_account(client: FiuaiSDK, bank_account_no: str) -> Optional[Company
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
# 获取银行信息
|
|
61
|
-
_bank_detail = client.
|
|
61
|
+
_bank_detail = client.internal_get(
|
|
62
62
|
doctype="Bank",
|
|
63
63
|
name=_bank_account["bank"],
|
|
64
64
|
fields=["name", "bank_name"]
|
|
@@ -68,7 +68,7 @@ def get_bank_account(client: FiuaiSDK, bank_account_no: str) -> Optional[Company
|
|
|
68
68
|
return CompanyBankAccountSearchResult(exists=False)
|
|
69
69
|
|
|
70
70
|
# 获取支行信息
|
|
71
|
-
_bank_branch = client.
|
|
71
|
+
_bank_branch = client.internal_get(
|
|
72
72
|
doctype="Bank Branch",
|
|
73
73
|
name=_bank_account["opening_bank_branch"],
|
|
74
74
|
fields=["name", "bank_branch_name"]
|
|
@@ -93,7 +93,7 @@ def get_bank_account_by_bank_id(client: FiuaiSDK, bank_id: str) -> BankAccount|N
|
|
|
93
93
|
"""
|
|
94
94
|
根据银行账号id获取银行账号信息
|
|
95
95
|
"""
|
|
96
|
-
resp = client.
|
|
96
|
+
resp = client.internal_get(
|
|
97
97
|
doctype="Bank Account",
|
|
98
98
|
filters=[["name", "=", bank_id]],
|
|
99
99
|
fields=[
|
|
@@ -111,7 +111,7 @@ def get_bank_account_by_bank_id(client: FiuaiSDK, bank_id: str) -> BankAccount|N
|
|
|
111
111
|
# 获取支行信息
|
|
112
112
|
bank_branch = ""
|
|
113
113
|
if resp["opening_bank_branch"] != "":
|
|
114
|
-
_bank_branch = client.
|
|
114
|
+
_bank_branch = client.internal_get(
|
|
115
115
|
doctype="Bank Branch",
|
|
116
116
|
name=resp["opening_bank_branch"],
|
|
117
117
|
fields=["name", "bank_branch_name"]
|
|
@@ -123,7 +123,7 @@ def get_bank_account_by_bank_id(client: FiuaiSDK, bank_id: str) -> BankAccount|N
|
|
|
123
123
|
bank_branch = _bank_branch["bank_branch_name"]
|
|
124
124
|
|
|
125
125
|
# 获取银行信息
|
|
126
|
-
_bank_detail = client.
|
|
126
|
+
_bank_detail = client.internal_get(
|
|
127
127
|
doctype="Bank",
|
|
128
128
|
name=resp["bank"],
|
|
129
129
|
fields=["name", "bank_name"]
|