Refa: Move HTTP API tests to top-level test directory (#8042)

### What problem does this PR solve?

Move test cases only - CI still runs tests under sdk/python

### Type of change

- [x] Refactoring
This commit is contained in:
Liu An
2025-06-04 13:16:17 +08:00
committed by GitHub
parent b832372c98
commit 52c814b89d
39 changed files with 7934 additions and 6 deletions

View File

@ -0,0 +1,46 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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 common import create_chat_assistant, delete_chat_assistants, list_documnets, parse_documnets
from utils import wait_for
@wait_for(30, 1, "Document parsing timeout")
def condition(_auth, _dataset_id):
res = list_documnets(_auth, _dataset_id)
for doc in res["data"]["docs"]:
if doc["run"] != "DONE":
return False
return True
@pytest.fixture(scope="function")
def add_chat_assistants_func(request, api_key, add_document):
def cleanup():
delete_chat_assistants(api_key)
request.addfinalizer(cleanup)
dataset_id, document_id = add_document
parse_documnets(api_key, dataset_id, {"document_ids": [document_id]})
condition(api_key, dataset_id)
chat_assistant_ids = []
for i in range(5):
res = create_chat_assistant(api_key, {"name": f"test_chat_assistant_{i}", "dataset_ids": [dataset_id]})
chat_assistant_ids.append(res["data"]["id"])
return dataset_id, document_id, chat_assistant_ids

View File

@ -0,0 +1,241 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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 common import CHAT_ASSISTANT_NAME_LIMIT, INVALID_API_TOKEN, create_chat_assistant
from libs.auth import RAGFlowHttpApiAuth
from utils import encode_avatar
from utils.file_utils import create_image_file
@pytest.mark.p1
class TestAuthorization:
@pytest.mark.parametrize(
"invalid_auth, expected_code, expected_message",
[
(None, 0, "`Authorization` can't be empty"),
(
RAGFlowHttpApiAuth(INVALID_API_TOKEN),
109,
"Authentication error: API key is invalid!",
),
],
)
def test_invalid_auth(self, invalid_auth, expected_code, expected_message):
res = create_chat_assistant(invalid_auth)
assert res["code"] == expected_code
assert res["message"] == expected_message
@pytest.mark.usefixtures("clear_chat_assistants")
class TestChatAssistantCreate:
@pytest.mark.p1
@pytest.mark.parametrize(
"payload, expected_code, expected_message",
[
({"name": "valid_name"}, 0, ""),
pytest.param({"name": "a" * (CHAT_ASSISTANT_NAME_LIMIT + 1)}, 102, "", marks=pytest.mark.skip(reason="issues/")),
pytest.param({"name": 1}, 100, "", marks=pytest.mark.skip(reason="issues/")),
({"name": ""}, 102, "`name` is required."),
({"name": "duplicated_name"}, 102, "Duplicated chat name in creating chat."),
({"name": "case insensitive"}, 102, "Duplicated chat name in creating chat."),
],
)
def test_name(self, api_key, add_chunks, payload, expected_code, expected_message):
payload["dataset_ids"] = [] # issues/
if payload["name"] == "duplicated_name":
create_chat_assistant(api_key, payload)
elif payload["name"] == "case insensitive":
create_chat_assistant(api_key, {"name": payload["name"].upper()})
res = create_chat_assistant(api_key, payload)
assert res["code"] == expected_code, res
if expected_code == 0:
assert res["data"]["name"] == payload["name"]
else:
assert res["message"] == expected_message
@pytest.mark.p1
@pytest.mark.parametrize(
"dataset_ids, expected_code, expected_message",
[
([], 0, ""),
(lambda r: [r], 0, ""),
(["invalid_dataset_id"], 102, "You don't own the dataset invalid_dataset_id"),
("invalid_dataset_id", 102, "You don't own the dataset i"),
],
)
def test_dataset_ids(self, api_key, add_chunks, dataset_ids, expected_code, expected_message):
dataset_id, _, _ = add_chunks
payload = {"name": "ragflow test"}
if callable(dataset_ids):
payload["dataset_ids"] = dataset_ids(dataset_id)
else:
payload["dataset_ids"] = dataset_ids
res = create_chat_assistant(api_key, payload)
assert res["code"] == expected_code, res
if expected_code == 0:
assert res["data"]["name"] == payload["name"]
else:
assert res["message"] == expected_message
@pytest.mark.p3
def test_avatar(self, api_key, tmp_path):
fn = create_image_file(tmp_path / "ragflow_test.png")
payload = {"name": "avatar_test", "avatar": encode_avatar(fn), "dataset_ids": []}
res = create_chat_assistant(api_key, payload)
assert res["code"] == 0
@pytest.mark.p2
@pytest.mark.parametrize(
"llm, expected_code, expected_message",
[
({}, 0, ""),
({"model_name": "glm-4"}, 0, ""),
({"model_name": "unknown"}, 102, "`model_name` unknown doesn't exist"),
({"temperature": 0}, 0, ""),
({"temperature": 1}, 0, ""),
pytest.param({"temperature": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"temperature": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"temperature": "a"}, 0, "", marks=pytest.mark.skip),
({"top_p": 0}, 0, ""),
({"top_p": 1}, 0, ""),
pytest.param({"top_p": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"top_p": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"top_p": "a"}, 0, "", marks=pytest.mark.skip),
({"presence_penalty": 0}, 0, ""),
({"presence_penalty": 1}, 0, ""),
pytest.param({"presence_penalty": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"presence_penalty": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"presence_penalty": "a"}, 0, "", marks=pytest.mark.skip),
({"frequency_penalty": 0}, 0, ""),
({"frequency_penalty": 1}, 0, ""),
pytest.param({"frequency_penalty": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"frequency_penalty": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"frequency_penalty": "a"}, 0, "", marks=pytest.mark.skip),
({"max_token": 0}, 0, ""),
({"max_token": 1024}, 0, ""),
pytest.param({"max_token": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"max_token": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"max_token": "a"}, 0, "", marks=pytest.mark.skip),
pytest.param({"unknown": "unknown"}, 0, "", marks=pytest.mark.skip),
],
)
def test_llm(self, api_key, add_chunks, llm, expected_code, expected_message):
dataset_id, _, _ = add_chunks
payload = {"name": "llm_test", "dataset_ids": [dataset_id], "llm": llm}
res = create_chat_assistant(api_key, payload)
assert res["code"] == expected_code
if expected_code == 0:
if llm:
for k, v in llm.items():
assert res["data"]["llm"][k] == v
else:
assert res["data"]["llm"]["model_name"] == "glm-4-flash@ZHIPU-AI"
assert res["data"]["llm"]["temperature"] == 0.1
assert res["data"]["llm"]["top_p"] == 0.3
assert res["data"]["llm"]["presence_penalty"] == 0.4
assert res["data"]["llm"]["frequency_penalty"] == 0.7
assert res["data"]["llm"]["max_tokens"] == 512
else:
assert res["message"] == expected_message
@pytest.mark.p2
@pytest.mark.parametrize(
"prompt, expected_code, expected_message",
[
({}, 0, ""),
({"similarity_threshold": 0}, 0, ""),
({"similarity_threshold": 1}, 0, ""),
pytest.param({"similarity_threshold": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"similarity_threshold": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"similarity_threshold": "a"}, 0, "", marks=pytest.mark.skip),
({"keywords_similarity_weight": 0}, 0, ""),
({"keywords_similarity_weight": 1}, 0, ""),
pytest.param({"keywords_similarity_weight": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"keywords_similarity_weight": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"keywords_similarity_weight": "a"}, 0, "", marks=pytest.mark.skip),
({"variables": []}, 0, ""),
({"top_n": 0}, 0, ""),
({"top_n": 1}, 0, ""),
pytest.param({"top_n": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"top_n": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"top_n": "a"}, 0, "", marks=pytest.mark.skip),
({"empty_response": "Hello World"}, 0, ""),
({"empty_response": ""}, 0, ""),
({"empty_response": "!@#$%^&*()"}, 0, ""),
({"empty_response": "中文测试"}, 0, ""),
pytest.param({"empty_response": 123}, 0, "", marks=pytest.mark.skip),
pytest.param({"empty_response": True}, 0, "", marks=pytest.mark.skip),
pytest.param({"empty_response": " "}, 0, "", marks=pytest.mark.skip),
({"opener": "Hello World"}, 0, ""),
({"opener": ""}, 0, ""),
({"opener": "!@#$%^&*()"}, 0, ""),
({"opener": "中文测试"}, 0, ""),
pytest.param({"opener": 123}, 0, "", marks=pytest.mark.skip),
pytest.param({"opener": True}, 0, "", marks=pytest.mark.skip),
pytest.param({"opener": " "}, 0, "", marks=pytest.mark.skip),
({"show_quote": True}, 0, ""),
({"show_quote": False}, 0, ""),
({"prompt": "Hello World {knowledge}"}, 0, ""),
({"prompt": "{knowledge}"}, 0, ""),
({"prompt": "!@#$%^&*() {knowledge}"}, 0, ""),
({"prompt": "中文测试 {knowledge}"}, 0, ""),
({"prompt": "Hello World"}, 102, "Parameter 'knowledge' is not used"),
({"prompt": "Hello World", "variables": []}, 0, ""),
pytest.param({"prompt": 123}, 100, """AttributeError("\'int\' object has no attribute \'find\'")""", marks=pytest.mark.skip),
pytest.param({"prompt": True}, 100, """AttributeError("\'int\' object has no attribute \'find\'")""", marks=pytest.mark.skip),
pytest.param({"unknown": "unknown"}, 0, "", marks=pytest.mark.skip),
],
)
def test_prompt(self, api_key, add_chunks, prompt, expected_code, expected_message):
dataset_id, _, _ = add_chunks
payload = {"name": "prompt_test", "dataset_ids": [dataset_id], "prompt": prompt}
res = create_chat_assistant(api_key, payload)
assert res["code"] == expected_code
if expected_code == 0:
if prompt:
for k, v in prompt.items():
if k == "keywords_similarity_weight":
assert res["data"]["prompt"][k] == 1 - v
else:
assert res["data"]["prompt"][k] == v
else:
assert res["data"]["prompt"]["similarity_threshold"] == 0.2
assert res["data"]["prompt"]["keywords_similarity_weight"] == 0.7
assert res["data"]["prompt"]["top_n"] == 6
assert res["data"]["prompt"]["variables"] == [{"key": "knowledge", "optional": False}]
assert res["data"]["prompt"]["rerank_model"] == ""
assert res["data"]["prompt"]["empty_response"] == "Sorry! No relevant content was found in the knowledge base!"
assert res["data"]["prompt"]["opener"] == "Hi! I'm your assistant, what can I do for you?"
assert res["data"]["prompt"]["show_quote"] is True
assert (
res["data"]["prompt"]["prompt"]
== 'You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence "The answer you are looking for is not found in the knowledge base!" Answers need to consider chat history.\n Here is the knowledge base:\n {knowledge}\n The above is the knowledge base.'
)
else:
assert res["message"] == expected_message
class TestChatAssistantCreate2:
@pytest.mark.p2
def test_unparsed_document(self, api_key, add_document):
dataset_id, _ = add_document
payload = {"name": "prompt_test", "dataset_ids": [dataset_id]}
res = create_chat_assistant(api_key, payload)
assert res["code"] == 102
assert "doesn't own parsed file" in res["message"]

View File

@ -0,0 +1,124 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
#
from concurrent.futures import ThreadPoolExecutor
import pytest
from common import INVALID_API_TOKEN, batch_create_chat_assistants, delete_chat_assistants, list_chat_assistants
from libs.auth import RAGFlowHttpApiAuth
@pytest.mark.p1
class TestAuthorization:
@pytest.mark.parametrize(
"invalid_auth, expected_code, expected_message",
[
(None, 0, "`Authorization` can't be empty"),
(
RAGFlowHttpApiAuth(INVALID_API_TOKEN),
109,
"Authentication error: API key is invalid!",
),
],
)
def test_invalid_auth(self, invalid_auth, expected_code, expected_message):
res = delete_chat_assistants(invalid_auth)
assert res["code"] == expected_code
assert res["message"] == expected_message
class TestChatAssistantsDelete:
@pytest.mark.parametrize(
"payload, expected_code, expected_message, remaining",
[
pytest.param(None, 0, "", 0, marks=pytest.mark.p3),
pytest.param({"ids": []}, 0, "", 0, marks=pytest.mark.p3),
pytest.param({"ids": ["invalid_id"]}, 102, "Assistant(invalid_id) not found.", 5, marks=pytest.mark.p3),
pytest.param({"ids": ["\n!?。;!?\"'"]}, 102, """Assistant(\n!?。;!?"\') not found.""", 5, marks=pytest.mark.p3),
pytest.param("not json", 100, "AttributeError(\"'str' object has no attribute 'get'\")", 5, marks=pytest.mark.p3),
pytest.param(lambda r: {"ids": r[:1]}, 0, "", 4, marks=pytest.mark.p3),
pytest.param(lambda r: {"ids": r}, 0, "", 0, marks=pytest.mark.p1),
],
)
def test_basic_scenarios(self, api_key, add_chat_assistants_func, payload, expected_code, expected_message, remaining):
_, _, chat_assistant_ids = add_chat_assistants_func
if callable(payload):
payload = payload(chat_assistant_ids)
res = delete_chat_assistants(api_key, payload)
assert res["code"] == expected_code
if res["code"] != 0:
assert res["message"] == expected_message
res = list_chat_assistants(api_key)
assert len(res["data"]) == remaining
@pytest.mark.parametrize(
"payload",
[
pytest.param(lambda r: {"ids": ["invalid_id"] + r}, marks=pytest.mark.p3),
pytest.param(lambda r: {"ids": r[:1] + ["invalid_id"] + r[1:5]}, marks=pytest.mark.p1),
pytest.param(lambda r: {"ids": r + ["invalid_id"]}, marks=pytest.mark.p3),
],
)
def test_delete_partial_invalid_id(self, api_key, add_chat_assistants_func, payload):
_, _, chat_assistant_ids = add_chat_assistants_func
if callable(payload):
payload = payload(chat_assistant_ids)
res = delete_chat_assistants(api_key, payload)
assert res["code"] == 0
assert res["data"]["errors"][0] == "Assistant(invalid_id) not found."
assert res["data"]["success_count"] == 5
res = list_chat_assistants(api_key)
assert len(res["data"]) == 0
@pytest.mark.p3
def test_repeated_deletion(self, api_key, add_chat_assistants_func):
_, _, chat_assistant_ids = add_chat_assistants_func
res = delete_chat_assistants(api_key, {"ids": chat_assistant_ids})
assert res["code"] == 0
res = delete_chat_assistants(api_key, {"ids": chat_assistant_ids})
assert res["code"] == 102
assert "not found" in res["message"]
@pytest.mark.p3
def test_duplicate_deletion(self, api_key, add_chat_assistants_func):
_, _, chat_assistant_ids = add_chat_assistants_func
res = delete_chat_assistants(api_key, {"ids": chat_assistant_ids + chat_assistant_ids})
assert res["code"] == 0
assert "Duplicate assistant ids" in res["data"]["errors"][0]
assert res["data"]["success_count"] == 5
res = list_chat_assistants(api_key)
assert res["code"] == 0
@pytest.mark.p3
def test_concurrent_deletion(self, api_key):
ids = batch_create_chat_assistants(api_key, 100)
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(delete_chat_assistants, api_key, {"ids": ids[i : i + 1]}) for i in range(100)]
responses = [f.result() for f in futures]
assert all(r["code"] == 0 for r in responses)
@pytest.mark.p3
def test_delete_10k(self, api_key):
ids = batch_create_chat_assistants(api_key, 10_000)
res = delete_chat_assistants(api_key, {"ids": ids})
assert res["code"] == 0
res = list_chat_assistants(api_key)
assert len(res["data"]) == 0

View File

@ -0,0 +1,311 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
#
from concurrent.futures import ThreadPoolExecutor
import pytest
from common import INVALID_API_TOKEN, delete_datasets, list_chat_assistants
from libs.auth import RAGFlowHttpApiAuth
from utils import is_sorted
@pytest.mark.p1
class TestAuthorization:
@pytest.mark.parametrize(
"invalid_auth, expected_code, expected_message",
[
(None, 0, "`Authorization` can't be empty"),
(
RAGFlowHttpApiAuth(INVALID_API_TOKEN),
109,
"Authentication error: API key is invalid!",
),
],
)
def test_invalid_auth(self, invalid_auth, expected_code, expected_message):
res = list_chat_assistants(invalid_auth)
assert res["code"] == expected_code
assert res["message"] == expected_message
@pytest.mark.usefixtures("add_chat_assistants")
class TestChatAssistantsList:
@pytest.mark.p1
def test_default(self, api_key):
res = list_chat_assistants(api_key)
assert res["code"] == 0
assert len(res["data"]) == 5
@pytest.mark.p1
@pytest.mark.parametrize(
"params, expected_code, expected_page_size, expected_message",
[
({"page": None, "page_size": 2}, 0, 2, ""),
({"page": 0, "page_size": 2}, 0, 2, ""),
({"page": 2, "page_size": 2}, 0, 2, ""),
({"page": 3, "page_size": 2}, 0, 1, ""),
({"page": "3", "page_size": 2}, 0, 1, ""),
pytest.param(
{"page": -1, "page_size": 2},
100,
0,
"1064",
marks=pytest.mark.skip(reason="issues/5851"),
),
pytest.param(
{"page": "a", "page_size": 2},
100,
0,
"""ValueError("invalid literal for int() with base 10: \'a\'")""",
marks=pytest.mark.skip(reason="issues/5851"),
),
],
)
def test_page(self, api_key, params, expected_code, expected_page_size, expected_message):
res = list_chat_assistants(api_key, params=params)
assert res["code"] == expected_code
if expected_code == 0:
assert len(res["data"]) == expected_page_size
else:
assert res["message"] == expected_message
@pytest.mark.p1
@pytest.mark.parametrize(
"params, expected_code, expected_page_size, expected_message",
[
({"page_size": None}, 0, 5, ""),
({"page_size": 0}, 0, 0, ""),
({"page_size": 1}, 0, 1, ""),
({"page_size": 6}, 0, 5, ""),
({"page_size": "1"}, 0, 1, ""),
pytest.param(
{"page_size": -1},
100,
0,
"1064",
marks=pytest.mark.skip(reason="issues/5851"),
),
pytest.param(
{"page_size": "a"},
100,
0,
"""ValueError("invalid literal for int() with base 10: \'a\'")""",
marks=pytest.mark.skip(reason="issues/5851"),
),
],
)
def test_page_size(
self,
api_key,
params,
expected_code,
expected_page_size,
expected_message,
):
res = list_chat_assistants(api_key, params=params)
assert res["code"] == expected_code
if expected_code == 0:
assert len(res["data"]) == expected_page_size
else:
assert res["message"] == expected_message
@pytest.mark.p3
@pytest.mark.parametrize(
"params, expected_code, assertions, expected_message",
[
({"orderby": None}, 0, lambda r: (is_sorted(r["data"], "create_time", True)), ""),
({"orderby": "create_time"}, 0, lambda r: (is_sorted(r["data"], "create_time", True)), ""),
({"orderby": "update_time"}, 0, lambda r: (is_sorted(r["data"], "update_time", True)), ""),
pytest.param(
{"orderby": "name", "desc": "False"},
0,
lambda r: (is_sorted(r["data"], "name", False)),
"",
marks=pytest.mark.skip(reason="issues/5851"),
),
pytest.param(
{"orderby": "unknown"},
102,
0,
"orderby should be create_time or update_time",
marks=pytest.mark.skip(reason="issues/5851"),
),
],
)
def test_orderby(
self,
api_key,
params,
expected_code,
assertions,
expected_message,
):
res = list_chat_assistants(api_key, params=params)
assert res["code"] == expected_code
if expected_code == 0:
if callable(assertions):
assert assertions(res)
else:
assert res["message"] == expected_message
@pytest.mark.p3
@pytest.mark.parametrize(
"params, expected_code, assertions, expected_message",
[
({"desc": None}, 0, lambda r: (is_sorted(r["data"], "create_time", True)), ""),
({"desc": "true"}, 0, lambda r: (is_sorted(r["data"], "create_time", True)), ""),
({"desc": "True"}, 0, lambda r: (is_sorted(r["data"], "create_time", True)), ""),
({"desc": True}, 0, lambda r: (is_sorted(r["data"], "create_time", True)), ""),
({"desc": "false"}, 0, lambda r: (is_sorted(r["data"], "create_time", False)), ""),
({"desc": "False"}, 0, lambda r: (is_sorted(r["data"], "create_time", False)), ""),
({"desc": False}, 0, lambda r: (is_sorted(r["data"], "create_time", False)), ""),
({"desc": "False", "orderby": "update_time"}, 0, lambda r: (is_sorted(r["data"], "update_time", False)), ""),
pytest.param(
{"desc": "unknown"},
102,
0,
"desc should be true or false",
marks=pytest.mark.skip(reason="issues/5851"),
),
],
)
def test_desc(
self,
api_key,
params,
expected_code,
assertions,
expected_message,
):
res = list_chat_assistants(api_key, params=params)
assert res["code"] == expected_code
if expected_code == 0:
if callable(assertions):
assert assertions(res)
else:
assert res["message"] == expected_message
@pytest.mark.p1
@pytest.mark.parametrize(
"params, expected_code, expected_num, expected_message",
[
({"name": None}, 0, 5, ""),
({"name": ""}, 0, 5, ""),
({"name": "test_chat_assistant_1"}, 0, 1, ""),
({"name": "unknown"}, 102, 0, "The chat doesn't exist"),
],
)
def test_name(self, api_key, params, expected_code, expected_num, expected_message):
res = list_chat_assistants(api_key, params=params)
assert res["code"] == expected_code
if expected_code == 0:
if params["name"] in [None, ""]:
assert len(res["data"]) == expected_num
else:
assert res["data"][0]["name"] == params["name"]
else:
assert res["message"] == expected_message
@pytest.mark.p1
@pytest.mark.parametrize(
"chat_assistant_id, expected_code, expected_num, expected_message",
[
(None, 0, 5, ""),
("", 0, 5, ""),
(lambda r: r[0], 0, 1, ""),
("unknown", 102, 0, "The chat doesn't exist"),
],
)
def test_id(
self,
api_key,
add_chat_assistants,
chat_assistant_id,
expected_code,
expected_num,
expected_message,
):
_, _, chat_assistant_ids = add_chat_assistants
if callable(chat_assistant_id):
params = {"id": chat_assistant_id(chat_assistant_ids)}
else:
params = {"id": chat_assistant_id}
res = list_chat_assistants(api_key, params=params)
assert res["code"] == expected_code
if expected_code == 0:
if params["id"] in [None, ""]:
assert len(res["data"]) == expected_num
else:
assert res["data"][0]["id"] == params["id"]
else:
assert res["message"] == expected_message
@pytest.mark.p3
@pytest.mark.parametrize(
"chat_assistant_id, name, expected_code, expected_num, expected_message",
[
(lambda r: r[0], "test_chat_assistant_0", 0, 1, ""),
(lambda r: r[0], "test_chat_assistant_1", 102, 0, "The chat doesn't exist"),
(lambda r: r[0], "unknown", 102, 0, "The chat doesn't exist"),
("id", "chat_assistant_0", 102, 0, "The chat doesn't exist"),
],
)
def test_name_and_id(
self,
api_key,
add_chat_assistants,
chat_assistant_id,
name,
expected_code,
expected_num,
expected_message,
):
_, _, chat_assistant_ids = add_chat_assistants
if callable(chat_assistant_id):
params = {"id": chat_assistant_id(chat_assistant_ids), "name": name}
else:
params = {"id": chat_assistant_id, "name": name}
res = list_chat_assistants(api_key, params=params)
assert res["code"] == expected_code
if expected_code == 0:
assert len(res["data"]) == expected_num
else:
assert res["message"] == expected_message
@pytest.mark.p3
def test_concurrent_list(self, api_key):
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(list_chat_assistants, api_key) for i in range(100)]
responses = [f.result() for f in futures]
assert all(r["code"] == 0 for r in responses)
@pytest.mark.p3
def test_invalid_params(self, api_key):
params = {"a": "b"}
res = list_chat_assistants(api_key, params=params)
assert res["code"] == 0
assert len(res["data"]) == 5
@pytest.mark.p2
def test_list_chats_after_deleting_associated_dataset(self, api_key, add_chat_assistants):
dataset_id, _, _ = add_chat_assistants
res = delete_datasets(api_key, {"ids": [dataset_id]})
assert res["code"] == 0
res = list_chat_assistants(api_key)
assert res["code"] == 0
assert len(res["data"]) == 5

View File

@ -0,0 +1,228 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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 common import CHAT_ASSISTANT_NAME_LIMIT, INVALID_API_TOKEN, list_chat_assistants, update_chat_assistant
from libs.auth import RAGFlowHttpApiAuth
from utils import encode_avatar
from utils.file_utils import create_image_file
@pytest.mark.p1
class TestAuthorization:
@pytest.mark.parametrize(
"invalid_auth, expected_code, expected_message",
[
(None, 0, "`Authorization` can't be empty"),
(
RAGFlowHttpApiAuth(INVALID_API_TOKEN),
109,
"Authentication error: API key is invalid!",
),
],
)
def test_invalid_auth(self, invalid_auth, expected_code, expected_message):
res = update_chat_assistant(invalid_auth, "chat_assistant_id")
assert res["code"] == expected_code
assert res["message"] == expected_message
class TestChatAssistantUpdate:
@pytest.mark.parametrize(
"payload, expected_code, expected_message",
[
pytest.param({"name": "valid_name"}, 0, "", marks=pytest.mark.p1),
pytest.param({"name": "a" * (CHAT_ASSISTANT_NAME_LIMIT + 1)}, 102, "", marks=pytest.mark.skip(reason="issues/")),
pytest.param({"name": 1}, 100, "", marks=pytest.mark.skip(reason="issues/")),
pytest.param({"name": ""}, 102, "`name` cannot be empty.", marks=pytest.mark.p3),
pytest.param({"name": "test_chat_assistant_1"}, 102, "Duplicated chat name in updating chat.", marks=pytest.mark.p3),
pytest.param({"name": "TEST_CHAT_ASSISTANT_1"}, 102, "Duplicated chat name in updating chat.", marks=pytest.mark.p3),
],
)
def test_name(self, api_key, add_chat_assistants_func, payload, expected_code, expected_message):
_, _, chat_assistant_ids = add_chat_assistants_func
res = update_chat_assistant(api_key, chat_assistant_ids[0], payload)
assert res["code"] == expected_code, res
if expected_code == 0:
res = list_chat_assistants(api_key, {"id": chat_assistant_ids[0]})
assert res["data"][0]["name"] == payload.get("name")
else:
assert res["message"] == expected_message
@pytest.mark.parametrize(
"dataset_ids, expected_code, expected_message",
[
pytest.param([], 0, "", marks=pytest.mark.skip(reason="issues/")),
pytest.param(lambda r: [r], 0, "", marks=pytest.mark.p1),
pytest.param(["invalid_dataset_id"], 102, "You don't own the dataset invalid_dataset_id", marks=pytest.mark.p3),
pytest.param("invalid_dataset_id", 102, "You don't own the dataset i", marks=pytest.mark.p3),
],
)
def test_dataset_ids(self, api_key, add_chat_assistants_func, dataset_ids, expected_code, expected_message):
dataset_id, _, chat_assistant_ids = add_chat_assistants_func
payload = {"name": "ragflow test"}
if callable(dataset_ids):
payload["dataset_ids"] = dataset_ids(dataset_id)
else:
payload["dataset_ids"] = dataset_ids
res = update_chat_assistant(api_key, chat_assistant_ids[0], payload)
assert res["code"] == expected_code, res
if expected_code == 0:
res = list_chat_assistants(api_key, {"id": chat_assistant_ids[0]})
assert res["data"][0]["name"] == payload.get("name")
else:
assert res["message"] == expected_message
@pytest.mark.p3
def test_avatar(self, api_key, add_chat_assistants_func, tmp_path):
dataset_id, _, chat_assistant_ids = add_chat_assistants_func
fn = create_image_file(tmp_path / "ragflow_test.png")
payload = {"name": "avatar_test", "avatar": encode_avatar(fn), "dataset_ids": [dataset_id]}
res = update_chat_assistant(api_key, chat_assistant_ids[0], payload)
assert res["code"] == 0
@pytest.mark.p3
@pytest.mark.parametrize(
"llm, expected_code, expected_message",
[
({}, 100, "ValueError"),
({"model_name": "glm-4"}, 0, ""),
({"model_name": "unknown"}, 102, "`model_name` unknown doesn't exist"),
({"temperature": 0}, 0, ""),
({"temperature": 1}, 0, ""),
pytest.param({"temperature": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"temperature": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"temperature": "a"}, 0, "", marks=pytest.mark.skip),
({"top_p": 0}, 0, ""),
({"top_p": 1}, 0, ""),
pytest.param({"top_p": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"top_p": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"top_p": "a"}, 0, "", marks=pytest.mark.skip),
({"presence_penalty": 0}, 0, ""),
({"presence_penalty": 1}, 0, ""),
pytest.param({"presence_penalty": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"presence_penalty": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"presence_penalty": "a"}, 0, "", marks=pytest.mark.skip),
({"frequency_penalty": 0}, 0, ""),
({"frequency_penalty": 1}, 0, ""),
pytest.param({"frequency_penalty": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"frequency_penalty": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"frequency_penalty": "a"}, 0, "", marks=pytest.mark.skip),
({"max_token": 0}, 0, ""),
({"max_token": 1024}, 0, ""),
pytest.param({"max_token": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"max_token": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"max_token": "a"}, 0, "", marks=pytest.mark.skip),
pytest.param({"unknown": "unknown"}, 0, "", marks=pytest.mark.skip),
],
)
def test_llm(self, api_key, add_chat_assistants_func, llm, expected_code, expected_message):
dataset_id, _, chat_assistant_ids = add_chat_assistants_func
payload = {"name": "llm_test", "dataset_ids": [dataset_id], "llm": llm}
res = update_chat_assistant(api_key, chat_assistant_ids[0], payload)
assert res["code"] == expected_code
if expected_code == 0:
res = list_chat_assistants(api_key, {"id": chat_assistant_ids[0]})
if llm:
for k, v in llm.items():
assert res["data"][0]["llm"][k] == v
else:
assert res["data"][0]["llm"]["model_name"] == "glm-4-flash@ZHIPU-AI"
assert res["data"][0]["llm"]["temperature"] == 0.1
assert res["data"][0]["llm"]["top_p"] == 0.3
assert res["data"][0]["llm"]["presence_penalty"] == 0.4
assert res["data"][0]["llm"]["frequency_penalty"] == 0.7
assert res["data"][0]["llm"]["max_tokens"] == 512
else:
assert expected_message in res["message"]
@pytest.mark.p3
@pytest.mark.parametrize(
"prompt, expected_code, expected_message",
[
({}, 100, "ValueError"),
({"similarity_threshold": 0}, 0, ""),
({"similarity_threshold": 1}, 0, ""),
pytest.param({"similarity_threshold": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"similarity_threshold": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"similarity_threshold": "a"}, 0, "", marks=pytest.mark.skip),
({"keywords_similarity_weight": 0}, 0, ""),
({"keywords_similarity_weight": 1}, 0, ""),
pytest.param({"keywords_similarity_weight": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"keywords_similarity_weight": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"keywords_similarity_weight": "a"}, 0, "", marks=pytest.mark.skip),
({"variables": []}, 0, ""),
({"top_n": 0}, 0, ""),
({"top_n": 1}, 0, ""),
pytest.param({"top_n": -1}, 0, "", marks=pytest.mark.skip),
pytest.param({"top_n": 10}, 0, "", marks=pytest.mark.skip),
pytest.param({"top_n": "a"}, 0, "", marks=pytest.mark.skip),
({"empty_response": "Hello World"}, 0, ""),
({"empty_response": ""}, 0, ""),
({"empty_response": "!@#$%^&*()"}, 0, ""),
({"empty_response": "中文测试"}, 0, ""),
pytest.param({"empty_response": 123}, 0, "", marks=pytest.mark.skip),
pytest.param({"empty_response": True}, 0, "", marks=pytest.mark.skip),
pytest.param({"empty_response": " "}, 0, "", marks=pytest.mark.skip),
({"opener": "Hello World"}, 0, ""),
({"opener": ""}, 0, ""),
({"opener": "!@#$%^&*()"}, 0, ""),
({"opener": "中文测试"}, 0, ""),
pytest.param({"opener": 123}, 0, "", marks=pytest.mark.skip),
pytest.param({"opener": True}, 0, "", marks=pytest.mark.skip),
pytest.param({"opener": " "}, 0, "", marks=pytest.mark.skip),
({"show_quote": True}, 0, ""),
({"show_quote": False}, 0, ""),
({"prompt": "Hello World {knowledge}"}, 0, ""),
({"prompt": "{knowledge}"}, 0, ""),
({"prompt": "!@#$%^&*() {knowledge}"}, 0, ""),
({"prompt": "中文测试 {knowledge}"}, 0, ""),
({"prompt": "Hello World"}, 102, "Parameter 'knowledge' is not used"),
({"prompt": "Hello World", "variables": []}, 0, ""),
pytest.param({"prompt": 123}, 100, """AttributeError("\'int\' object has no attribute \'find\'")""", marks=pytest.mark.skip),
pytest.param({"prompt": True}, 100, """AttributeError("\'int\' object has no attribute \'find\'")""", marks=pytest.mark.skip),
pytest.param({"unknown": "unknown"}, 0, "", marks=pytest.mark.skip),
],
)
def test_prompt(self, api_key, add_chat_assistants_func, prompt, expected_code, expected_message):
dataset_id, _, chat_assistant_ids = add_chat_assistants_func
payload = {"name": "prompt_test", "dataset_ids": [dataset_id], "prompt": prompt}
res = update_chat_assistant(api_key, chat_assistant_ids[0], payload)
assert res["code"] == expected_code
if expected_code == 0:
res = list_chat_assistants(api_key, {"id": chat_assistant_ids[0]})
if prompt:
for k, v in prompt.items():
if k == "keywords_similarity_weight":
assert res["data"][0]["prompt"][k] == 1 - v
else:
assert res["data"][0]["prompt"][k] == v
else:
assert res["data"]["prompt"][0]["similarity_threshold"] == 0.2
assert res["data"]["prompt"][0]["keywords_similarity_weight"] == 0.7
assert res["data"]["prompt"][0]["top_n"] == 6
assert res["data"]["prompt"][0]["variables"] == [{"key": "knowledge", "optional": False}]
assert res["data"]["prompt"][0]["rerank_model"] == ""
assert res["data"]["prompt"][0]["empty_response"] == "Sorry! No relevant content was found in the knowledge base!"
assert res["data"]["prompt"][0]["opener"] == "Hi! I'm your assistant, what can I do for you?"
assert res["data"]["prompt"][0]["show_quote"] is True
assert (
res["data"]["prompt"][0]["prompt"]
== 'You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence "The answer you are looking for is not found in the knowledge base!" Answers need to consider chat history.\n Here is the knowledge base:\n {knowledge}\n The above is the knowledge base.'
)
else:
assert expected_message in res["message"]