Skip to content

Commit 85314ca

Browse files
taskingaijcSimsonW
authored andcommitted
test: add auto testcase for test project
add auto testcase for test project, contains assistant, inference, retrieval, tool service
1 parent e267921 commit 85314ca

25 files changed

+2689
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ target/
6767

6868
.swagger-codegen-ignore
6969
/.swagger-codegen/
70+
.DS_Store

test-requirements.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,15 @@ nose>=1.3.7
33
pluggy>=0.3.1
44
py>=1.4.31
55
randomize>=0.13
6+
pytest==7.4.3
7+
allure-pytest==2.13.2
8+
pytest-ordering==0.6
9+
pytest-xdist==3.5.0
10+
PyYAML==6.0.1
11+
pytest-assume==2.4.3
12+
pytest-asyncio==0.21.1
13+
asyncio==3.4.3
14+
pytest-tornasync>=0.6.0
15+
pytest-trio==0.8.0
16+
pytest-twisted==1.14.0
17+
Twisted==23.10.0

test/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
# coding: utf-8

test/common/app_auth.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import aiohttp
2+
from typing import List, Dict
3+
from test.common.utils import ResponseWrapper
4+
from test.config import Config
5+
6+
AUTH_BASE_URL = Config.SPACE_SERVICE_URL + "/v1/auth"
7+
8+
9+
async def login(data: Dict):
10+
# data = {email: str, password: str}
11+
async with aiohttp.ClientSession() as session:
12+
response = await session.post(AUTH_BASE_URL + "/login", json=data)
13+
return ResponseWrapper(response.status, await response.json())
14+
15+
BASE_URL = Config.PROJECT_SERVICE_URL
16+
17+
18+
# For GET /{space_id}/projects/list
19+
async def list_projects(token: str, space_id: str, data: Dict):
20+
# data = {"offset": int, "limit": int}
21+
headers = {"Authorization": "Bearer " + token}
22+
async with aiohttp.ClientSession(headers=headers) as session:
23+
request_url = f"{BASE_URL}/v1/{space_id}/projects/list"
24+
response = await session.get(request_url, params=data)
25+
return ResponseWrapper(response.status, await response.json())

test/common/logger.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import logging
2+
import time
3+
import os
4+
5+
BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
6+
7+
LOG_PATH = os.path.join(BASE_PATH, "log")
8+
if not os.path.exists(LOG_PATH):
9+
os.mkdir(LOG_PATH)
10+
11+
12+
class Logger:
13+
14+
def __init__(self):
15+
self.log_name = os.path.join(LOG_PATH, "{}.log".format(time.strftime("%Y%m%d")))
16+
self.logger = logging.getLogger("log")
17+
self.logger.setLevel(logging.DEBUG)
18+
19+
self.formatter = logging.Formatter(
20+
'[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s')
21+
22+
self.file_logger = logging.FileHandler(self.log_name, mode='a', encoding="UTF-8")
23+
self.console = logging.StreamHandler()
24+
self.console.setLevel(logging.DEBUG)
25+
self.file_logger.setLevel(logging.DEBUG)
26+
self.file_logger.setFormatter(self.formatter)
27+
self.console.setFormatter(self.formatter)
28+
self.logger.addHandler(self.file_logger)
29+
self.logger.addHandler(self.console)
30+
31+
32+
logger = Logger().logger
33+
34+
35+
def logger_info_base(http_code: str, http_status: str, res):
36+
logger.info("http_code ==>> except_res:{}, real_res:【 {} 】".format(http_code, res.status_code))
37+
logger.info("http_status ==>> except_res:{}, real_res:【 {} 】".format(http_status, res.json()["status"]))
38+
39+
40+
def logger_info(http_code: str, http_status: str, res):
41+
logger_info_base(http_code, http_status, res)
42+
logger.info("total_count ==>> real_res:【 {} 】".format(res.json()["total_count"]))
43+
logger.info("fetched_count ==>> real_res:【 {} 】".format(res.json()["fetched_count"]))
44+
45+
46+
def logger_ready_or_creating_info(http_code: str, http_status: str, res):
47+
logger_info_base(http_code, http_status, res)
48+
logger.info("data_status ==>> real_res:【 {} 】".format(res.json()["data"]["status"]))
49+
50+
51+
def logger_success_info(http_code: str, http_status: str, data_status, res):
52+
logger_info_base(http_code, http_status, res)
53+
logger.info("data_status ==>> except_res:{}, real_res:【 {} 】".format(data_status, res.json()["data"]["status"]))
54+
55+
56+
def logger_error_info(http_code: str, http_status: str, data_status, res):
57+
logger_info_base(http_code, http_status, res)
58+
logger.info("data_status ==>> except_res:{}, real_res:【 {} 】".format(data_status, res.json()["error"]["code"]))
59+
60+
61+

test/common/read_data.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import yaml
2+
import json
3+
import os
4+
from configparser import ConfigParser
5+
from test.common.logger import logger
6+
7+
8+
class MyConfigParser(ConfigParser):
9+
10+
def __init__(self, defaults=None):
11+
ConfigParser.__init__(self, defaults=defaults)
12+
13+
def optionxform(self, option_str):
14+
return option_str
15+
16+
def get_sections(self):
17+
return self._sections
18+
19+
20+
class ReadFileData:
21+
22+
def __init__(self):
23+
self.base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
24+
25+
def load_yaml(self, yaml_file_name):
26+
file_path = os.path.join(self.base_path, "data", yaml_file_name)
27+
logger.info("load {} file......".format(file_path))
28+
with open(file_path, encoding='utf-8') as f:
29+
_data = yaml.safe_load(f)
30+
logger.info("read data ==>> {} ".format(_data))
31+
return _data
32+
33+
def load_json(self):
34+
file_path = os.path.join(self.base_path, "config", "setting.json")
35+
logger.info("load {} file......".format(file_path))
36+
with open(file_path, encoding='utf-8') as f:
37+
_data = json.load(f)
38+
logger.info("read data ==>> {} ".format(_data))
39+
return _data
40+
41+
def load_ini(self):
42+
file_path = os.path.join(self.base_path, "config", "setting.ini")
43+
logger.info("load {} file......".format(file_path))
44+
config = MyConfigParser()
45+
config.read(file_path, encoding="UTF-8")
46+
_data = config.get_sections()
47+
# print("read data ==>> {} ".format(_data))
48+
return _data
49+
50+
def write_yml(self, yaml_file_name, _data):
51+
file_path = os.path.join(self.base_path, "data", yaml_file_name)
52+
logger.info("load {} file......".format(file_path))
53+
with open(file_path, 'wb') as f:
54+
logger.info("write data ==>> {} ".format(_data))
55+
yaml.safe_dump(_data, f, default_flow_style=False,
56+
explicit_start=True, allow_unicode=True, encoding='utf-8')
57+
58+
59+
data = ReadFileData()

test/common/utils.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
from typing import Dict, Any
2+
import asyncio
3+
import random
4+
import string
5+
import pytest
6+
7+
from test.common.logger import logger
8+
9+
10+
class ResponseWrapper:
11+
def __init__(self, status: int, json_data: Dict):
12+
self.status_code = status
13+
self._json_data = json_data
14+
15+
def json(self):
16+
return self._json_data
17+
18+
19+
def list_to_dict(data: list):
20+
d = {}
21+
for i in data:
22+
if isinstance(i, dict):
23+
for k, v in i.items():
24+
d[k] = v
25+
else:
26+
d.update(i)
27+
return d
28+
29+
30+
def get_random_email():
31+
domain = ["163.com", "126.com", "yeah.net", "vip.163.com", "vip.126.com", "188.com", "vip.188.com", "qq.com",
32+
"gmail.com", "yahoo.com", "sina.com", "sina.cn", "sohu.com", "sogou.com", "outlook.com", "189.com",
33+
"wo.cn", "139.com", "ailiyun.com", "icloud.com", "tianya.cn", "renren.com", "tom.com"]
34+
username_length = random.randint(5, 10)
35+
username = ''.join(random.choices(string.ascii_lowercase, k=username_length))
36+
domain_name = random.choice(domain)
37+
email = username + "@" + domain_name
38+
return email
39+
40+
41+
def get_password():
42+
return ''.join(random.choices(string.ascii_letters, k=7))+str(random.randint(0, 9))
43+
44+
45+
def assume(res, except_dict: Dict[str, Any]):
46+
pytest.assume(res.status_code == int(except_dict["except_http_code"]))
47+
pytest.assume(res.json()["status"] == except_dict["except_status"])
48+
49+
50+
def assume_success(res, except_dict: Dict[str, Any]):
51+
assume(res, except_dict)
52+
pytest.assume(res.json()["data"]["status"] == except_dict["except_data_status"])
53+
54+
55+
def assume_error(res, except_dict: Dict[str, Any]):
56+
assume(res, except_dict)
57+
pytest.assume(res.json()["error"]["code"] == except_dict["except_error_code"])
58+
59+
60+
def assume_count(res, except_dict: Dict[str, Any]):
61+
assume(res, except_dict)
62+
pytest.assume(res.json()["total_count"] >= 0)
63+
pytest.assume(res.json()["fetched_count"] >= 0)
64+
pytest.assume(len(res.json()["data"]) == res.json()["fetched_count"])
65+
66+
67+
def assume_assistant(res, assistant_dict: Dict[str, Any]):
68+
for key, value in assistant_dict.items():
69+
if key == "error":
70+
pass
71+
else:
72+
pytest.assume(res[key] == assistant_dict[key])
73+
74+
75+
def assume_collection(res, collection_dict: Dict[str, Any]):
76+
for key, value in collection_dict.items():
77+
if key == "configs":
78+
for k, v in value.items():
79+
pytest.assume(res.json()["data"][key][k] == collection_dict[key][k])
80+
elif key == "error":
81+
pass
82+
else:
83+
pytest.assume(res.json()["data"][key] == collection_dict[key])
84+
85+
86+
def assume_record(res, record_dict: Dict[str, Any]):
87+
for key, value in record_dict.items():
88+
if key == "type":
89+
pytest.assume(res.json()["data"][key] == record_dict[key])
90+
elif key == "error":
91+
pass
92+
elif key == "text":
93+
pytest.assume(res.json()["data"]["content"][key] == record_dict[key])
94+
95+
96+
def assume_chunk(res, chunk_dict: Dict[str, Any]):
97+
for key, value in chunk_dict.items():
98+
if key == "error":
99+
pass
100+
else:
101+
pytest.assume(len(res.json()["data"]) == res.json()["fetched_count"] == chunk_dict["top_k"])
102+
103+
104+
async def mq_execute_create_collection(n, func, *args, **kwargs):
105+
if n == 10:
106+
logger.info(func+"timeout")
107+
pytest.assume(False)
108+
else:
109+
from test.retrieval_service.collection import get_collection
110+
res = await get_collection(*args, **kwargs)
111+
if res.json()["data"]["status"] == "ready" and res.json()["data"]["num_records"] >= 0 and res.json()["data"]["num_chunks"] >= 0:
112+
return
113+
else:
114+
await asyncio.sleep(1)
115+
logger.info(f"collection status is {res.json()['data']['status']}")
116+
logger.info(f"collection num_records is {res.json()['data']['num_records']}")
117+
logger.info(f"collection num_chunks is {res.json()['data']['num_chunks']}")
118+
await mq_execute_create_collection(n+1, func, *args, **kwargs)
119+
120+
121+
async def mq_execute_delete_collection(n, func, *args, **kwargs):
122+
if n == 10:
123+
logger.info(func + "timeout")
124+
pytest.assume(False)
125+
else:
126+
from test.retrieval_service.collection import get_collection
127+
res = await get_collection(*args, **kwargs)
128+
if res.status_code == 404:
129+
logger.info(f"project[{args[0]}]collection[{args[1]}] is deleted")
130+
return
131+
else:
132+
await asyncio.sleep(1)
133+
await mq_execute_delete_collection(n+1, func, *args, **kwargs)
134+
135+
136+
async def mq_execute_create_record(n, func, *args, **kwargs):
137+
if n == 10:
138+
logger.info(func + "timeout")
139+
pytest.assume(False)
140+
else:
141+
from test.retrieval_service.record import get_record
142+
res = await get_record(*args, **kwargs)
143+
if res.json()["data"]["status"] == "ready" and res.json()["data"]["num_chunks"] >= 0:
144+
logger.info(f"project[{args[0]}]collection[{args[1]}]record[{args[2]}] status is ready")
145+
return
146+
elif res.json()["data"]["status"] == "partial" and res.json()["data"]["num_chunks"] >= 0:
147+
logger.info(f"project[{args[0]}]collection[{args[1]}]record[{args[2]}] is partial")
148+
return
149+
else:
150+
await asyncio.sleep(1)
151+
logger.info(f"record status is {res.json()['data']['status']}")
152+
logger.info(f"record num_chunks is {res.json()['data']['num_chunks']}")
153+
await mq_execute_create_record(n + 1, func, *args, **kwargs)
154+
155+
156+
async def mq_execute_delete_record(n, func, *args, **kwargs):
157+
if n == 10:
158+
logger.info(func + "timeout")
159+
pytest.assume(False)
160+
else:
161+
from test.retrieval_service.record import get_record
162+
res = await get_record(*args, **kwargs)
163+
if res.status_code == 404:
164+
logger.info(f"project[{args[0]}]collection[{args[1]}]record[{args[2]}] is deleted")
165+
return
166+
else:
167+
await asyncio.sleep(1)
168+
await mq_execute_delete_record(n + 1, func, *args, **kwargs)
169+
170+
171+
async def mq_execute_delete_project(n, func, *args, **kwargs):
172+
if n == 10:
173+
logger.info(func + "timeout")
174+
pytest.assume(False)
175+
else:
176+
from test.retrieval_service.collection import list_collections
177+
collections_res = await list_collections(*args, **kwargs)
178+
if collections_res.status_code == 400:
179+
return
180+
else:
181+
await asyncio.sleep(1)
182+
await mq_execute_delete_project(n + 1, func, *args, **kwargs)
183+
184+
185+
def get_random():
186+
return ''.join(random.choices(string.ascii_letters, k=7))+str(random.randint(0, 9))
187+
188+
189+
def get_project_id(i: int):
190+
return (get_random() for j in range(i))
191+
192+

test/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import os
2+
3+
# text_model_id = os.getenv("text_model_id")
4+
# chat_model_id = os.getenv("chat_model_id")
5+
6+
text_model_id = "jTJxv3iQ"
7+
chat_model_id = "Pxjizpc5"
8+
TASKINGAI_API_KEY = "taHAGKN5nNYdfAw4VtDVDXq7WXriyzsd"

0 commit comments

Comments
 (0)