diff --git a/test/testcases/test_web_api/common.py b/test/testcases/test_web_api/common.py index b9b75c1aa..c7ec156d1 100644 --- a/test/testcases/test_web_api/common.py +++ b/test/testcases/test_web_api/common.py @@ -25,7 +25,7 @@ HEADERS = {"Content-Type": "application/json"} KB_APP_URL = f"/{VERSION}/kb" DOCUMENT_APP_URL = f"/{VERSION}/document" CHUNK_API_URL = f"/{VERSION}/chunk" -# CHAT_ASSISTANT_API_URL = "/api/v1/chats" +DIALOG_APP_URL = f"/{VERSION}/dialog" # SESSION_WITH_CHAT_ASSISTANT_API_URL = "/api/v1/chats/{chat_id}/sessions" # SESSION_WITH_AGENT_API_URL = "/api/v1/agents/{agent_id}/sessions" @@ -201,3 +201,60 @@ def batch_add_chunks(auth, doc_id, num): res = add_chunk(auth, {"doc_id": doc_id, "content_with_weight": f"chunk test {i}"}) chunk_ids.append(res["data"]["chunk_id"]) return chunk_ids + + +# DIALOG APP +def create_dialog(auth, payload=None, *, headers=HEADERS, data=None): + res = requests.post(url=f"{HOST_ADDRESS}{DIALOG_APP_URL}/set", headers=headers, auth=auth, json=payload, data=data) + return res.json() + + +def update_dialog(auth, payload=None, *, headers=HEADERS, data=None): + res = requests.post(url=f"{HOST_ADDRESS}{DIALOG_APP_URL}/set", headers=headers, auth=auth, json=payload, data=data) + return res.json() + + +def get_dialog(auth, params=None, *, headers=HEADERS): + res = requests.get(url=f"{HOST_ADDRESS}{DIALOG_APP_URL}/get", headers=headers, auth=auth, params=params) + return res.json() + + +def list_dialogs(auth, *, headers=HEADERS): + res = requests.get(url=f"{HOST_ADDRESS}{DIALOG_APP_URL}/list", headers=headers, auth=auth) + return res.json() + + +def delete_dialog(auth, payload=None, *, headers=HEADERS, data=None): + res = requests.post(url=f"{HOST_ADDRESS}{DIALOG_APP_URL}/rm", headers=headers, auth=auth, json=payload, data=data) + return res.json() + + +def batch_create_dialogs(auth, num, kb_ids=None): + if kb_ids is None: + kb_ids = [] + + dialog_ids = [] + for i in range(num): + payload = { + "name": f"dialog_{i}", + "description": f"Test dialog {i}", + "kb_ids": kb_ids, + "prompt_config": {"system": "You are a helpful assistant. Use the following knowledge to answer questions: {knowledge}", "parameters": [{"key": "knowledge", "optional": False}]}, + "top_n": 6, + "top_k": 1024, + "similarity_threshold": 0.1, + "vector_similarity_weight": 0.3, + "llm_setting": {"model": "gpt-3.5-turbo", "temperature": 0.7}, + } + res = create_dialog(auth, payload) + if res["code"] == 0: + dialog_ids.append(res["data"]["id"]) + return dialog_ids + + +def delete_dialogs(auth): + res = list_dialogs(auth) + if res["code"] == 0 and res["data"]: + dialog_ids = [dialog["id"] for dialog in res["data"]] + if dialog_ids: + delete_dialog(auth, {"dialog_ids": dialog_ids}) diff --git a/test/testcases/test_web_api/conftest.py b/test/testcases/test_web_api/conftest.py index ebe0e6c29..18b56a845 100644 --- a/test/testcases/test_web_api/conftest.py +++ b/test/testcases/test_web_api/conftest.py @@ -21,6 +21,7 @@ from common import ( batch_create_datasets, bulk_upload_documents, delete_chunks, + delete_dialogs, list_chunks, list_documents, list_kbs, @@ -97,6 +98,14 @@ def clear_datasets(request: FixtureRequest, WebApiAuth: RAGFlowWebApiAuth): request.addfinalizer(cleanup) +@pytest.fixture(scope="function") +def clear_dialogs(request, WebApiAuth): + def cleanup(): + delete_dialogs(WebApiAuth) + + request.addfinalizer(cleanup) + + @pytest.fixture(scope="class") def add_dataset(request: FixtureRequest, WebApiAuth: RAGFlowWebApiAuth) -> str: def cleanup(): diff --git a/test/testcases/test_web_api/test_dialog_app/conftest.py b/test/testcases/test_web_api/test_dialog_app/conftest.py new file mode 100644 index 000000000..e2f142f7b --- /dev/null +++ b/test/testcases/test_web_api/test_dialog_app/conftest.py @@ -0,0 +1,50 @@ +# +# 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 batch_create_dialogs, delete_dialogs + + +@pytest.fixture(scope="function") +def add_dialog_func(request, WebApiAuth, add_dataset_func): + def cleanup(): + delete_dialogs(WebApiAuth) + + request.addfinalizer(cleanup) + + dataset_id = add_dataset_func + return dataset_id, batch_create_dialogs(WebApiAuth, 1, [dataset_id])[0] + + +@pytest.fixture(scope="class") +def add_dialogs(request, WebApiAuth, add_dataset): + def cleanup(): + delete_dialogs(WebApiAuth) + + request.addfinalizer(cleanup) + + dataset_id = add_dataset + return dataset_id, batch_create_dialogs(WebApiAuth, 5, [dataset_id]) + + +@pytest.fixture(scope="function") +def add_dialogs_func(request, WebApiAuth, add_dataset_func): + def cleanup(): + delete_dialogs(WebApiAuth) + + request.addfinalizer(cleanup) + + dataset_id = add_dataset_func + return dataset_id, batch_create_dialogs(WebApiAuth, 5, [dataset_id]) diff --git a/test/testcases/test_web_api/test_dialog_app/test_create_dialog.py b/test/testcases/test_web_api/test_dialog_app/test_create_dialog.py new file mode 100644 index 000000000..4e44586cf --- /dev/null +++ b/test/testcases/test_web_api/test_dialog_app/test_create_dialog.py @@ -0,0 +1,170 @@ +# +# 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, as_completed + +import pytest +from common import create_dialog +from configs import CHAT_ASSISTANT_NAME_LIMIT, INVALID_API_TOKEN +from hypothesis import example, given, settings +from libs.auth import RAGFlowWebApiAuth +from utils.hypothesis_utils import valid_names + + +@pytest.mark.usefixtures("clear_dialogs") +class TestAuthorization: + @pytest.mark.p1 + @pytest.mark.parametrize( + "invalid_auth, expected_code, expected_message", + [ + (None, 401, ""), + (RAGFlowWebApiAuth(INVALID_API_TOKEN), 401, ""), + ], + ids=["empty_auth", "invalid_api_token"], + ) + def test_auth_invalid(self, invalid_auth, expected_code, expected_message): + payload = {"name": "auth_test", "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = create_dialog(invalid_auth, payload) + assert res["code"] == expected_code, res + assert res["message"] == expected_message, res + + +@pytest.mark.usefixtures("clear_dialogs") +class TestCapability: + @pytest.mark.p3 + def test_create_dialog_100(self, WebApiAuth): + for i in range(100): + payload = {"name": f"dialog_{i}", "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, f"Failed to create dialog {i}" + + @pytest.mark.p3 + def test_create_dialog_concurrent(self, WebApiAuth): + count = 100 + with ThreadPoolExecutor(max_workers=5) as executor: + futures = [executor.submit(create_dialog, WebApiAuth, {"name": f"dialog_{i}", "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}}) for i in range(count)] + responses = list(as_completed(futures)) + assert len(responses) == count, responses + assert all(future.result()["code"] == 0 for future in futures) + + +@pytest.mark.usefixtures("clear_dialogs") +class TestDialogCreate: + @pytest.mark.p1 + @given(name=valid_names()) + @example("a" * CHAT_ASSISTANT_NAME_LIMIT) + @settings(max_examples=20) + def test_name(self, WebApiAuth, name): + payload = {"name": name, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.p2 + @pytest.mark.parametrize( + "name, expected_code, expected_message", + [ + ("", 102, "Dialog name can't be empty."), + (" ", 102, "Dialog name can't be empty."), + ("a" * (CHAT_ASSISTANT_NAME_LIMIT + 1), 102, "Dialog name length is 256 which is larger than 255"), + (0, 102, "Dialog name must be string."), + (None, 102, "Dialog name must be string."), + ], + ids=["empty_name", "space_name", "too_long_name", "invalid_name", "None_name"], + ) + def test_name_invalid(self, WebApiAuth, name, expected_code, expected_message): + payload = {"name": name, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == expected_code, res + assert res["message"] == expected_message, res + + @pytest.mark.p1 + def test_prompt_config_required(self, WebApiAuth): + payload = {"name": "test_dialog"} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 101, res + assert res["message"] == "required argument are missing: prompt_config; ", res + + @pytest.mark.p1 + def test_prompt_config_with_knowledge_no_kb(self, WebApiAuth): + payload = {"name": "test_dialog", "prompt_config": {"system": "You are a helpful assistant. Use this knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 102, res + assert "Please remove `{knowledge}` in system prompt" in res["message"], res + + @pytest.mark.p1 + def test_prompt_config_parameter_not_used(self, WebApiAuth): + payload = {"name": "test_dialog", "prompt_config": {"system": "You are a helpful assistant.", "parameters": [{"key": "unused_param", "optional": False}]}} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 102, res + assert "Parameter 'unused_param' is not used" in res["message"], res + + @pytest.mark.p1 + def test_create_with_kb_ids(self, WebApiAuth, add_dataset_func): + dataset_id = add_dataset_func + payload = { + "name": "test_dialog_with_kb", + "kb_ids": [dataset_id], + "prompt_config": {"system": "You are a helpful assistant. Use this knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["kb_ids"] == [dataset_id], res + + @pytest.mark.p2 + def test_create_with_all_parameters(self, WebApiAuth, add_dataset_func): + dataset_id = add_dataset_func + payload = { + "name": "comprehensive_dialog", + "description": "A comprehensive test dialog", + "icon": "🤖", + "kb_ids": [dataset_id], + "top_n": 10, + "top_k": 2048, + "rerank_id": "", + "similarity_threshold": 0.2, + "vector_similarity_weight": 0.5, + "llm_setting": {"model": "gpt-4", "temperature": 0.8, "max_tokens": 1000}, + "prompt_config": {"system": "You are a helpful assistant. Use this knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + data = res["data"] + assert data["name"] == "comprehensive_dialog", res + assert data["description"] == "A comprehensive test dialog", res + assert data["icon"] == "🤖", res + assert data["kb_ids"] == [dataset_id], res + assert data["top_n"] == 10, res + assert data["top_k"] == 2048, res + assert data["similarity_threshold"] == 0.2, res + assert data["vector_similarity_weight"] == 0.5, res + + @pytest.mark.p3 + def test_name_duplicated(self, WebApiAuth): + name = "duplicated_dialog" + payload = {"name": name, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.p2 + def test_optional_parameters(self, WebApiAuth): + payload = { + "name": "test_optional_params", + "prompt_config": {"system": "You are a helpful assistant. Optional param: {optional_param}", "parameters": [{"key": "optional_param", "optional": True}]}, + } + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res diff --git a/test/testcases/test_web_api/test_dialog_app/test_delete_dialogs.py b/test/testcases/test_web_api/test_dialog_app/test_delete_dialogs.py new file mode 100644 index 000000000..ab80d3c9a --- /dev/null +++ b/test/testcases/test_web_api/test_dialog_app/test_delete_dialogs.py @@ -0,0 +1,204 @@ +# +# 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, as_completed + +import pytest +from common import batch_create_dialogs, create_dialog, delete_dialog, list_dialogs +from configs import INVALID_API_TOKEN +from libs.auth import RAGFlowWebApiAuth + + +@pytest.mark.usefixtures("clear_dialogs") +class TestAuthorization: + @pytest.mark.p1 + @pytest.mark.parametrize( + "invalid_auth, expected_code, expected_message", + [ + (None, 401, ""), + (RAGFlowWebApiAuth(INVALID_API_TOKEN), 401, ""), + ], + ids=["empty_auth", "invalid_api_token"], + ) + def test_auth_invalid(self, invalid_auth, expected_code, expected_message, add_dialog_func): + _, dialog_id = add_dialog_func + payload = {"dialog_ids": [dialog_id]} + res = delete_dialog(invalid_auth, payload) + assert res["code"] == expected_code, res + assert res["message"] == expected_message, res + + +class TestDialogDelete: + @pytest.mark.p1 + def test_delete_single_dialog(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 1, res + + payload = {"dialog_ids": [dialog_id]} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"] is True, res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 0, res + + @pytest.mark.p1 + def test_delete_multiple_dialogs(self, WebApiAuth, add_dialogs_func): + _, dialog_ids = add_dialogs_func + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 5, res + + payload = {"dialog_ids": dialog_ids} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"] is True, res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 0, res + + @pytest.mark.p1 + def test_delete_partial_dialogs(self, WebApiAuth, add_dialogs_func): + _, dialog_ids = add_dialogs_func + + dialogs_to_delete = dialog_ids[:3] + payload = {"dialog_ids": dialogs_to_delete} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"] is True, res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 2, res + + remaining_ids = [dialog["id"] for dialog in res["data"]] + for dialog_id in dialog_ids[3:]: + assert dialog_id in remaining_ids, res + + @pytest.mark.p2 + def test_delete_nonexistent_dialog(self, WebApiAuth): + fake_dialog_id = "nonexistent_dialog_id" + payload = {"dialog_ids": [fake_dialog_id]} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 103, res + assert "Only owner of dialog authorized for this operation." in res["message"], res + + @pytest.mark.p2 + def test_delete_empty_dialog_ids(self, WebApiAuth): + payload = {"dialog_ids": []} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.p2 + def test_delete_missing_dialog_ids(self, WebApiAuth): + payload = {} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 101, res + assert res["message"] == "required argument are missing: dialog_ids; ", res + + @pytest.mark.p2 + def test_delete_invalid_dialog_ids_format(self, WebApiAuth): + payload = {"dialog_ids": "not_a_list"} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 103, res + assert res["message"] == "Only owner of dialog authorized for this operation.", res + + @pytest.mark.p2 + def test_delete_mixed_valid_invalid_dialogs(self, WebApiAuth, add_dialog_func): + _, valid_dialog_id = add_dialog_func + invalid_dialog_id = "nonexistent_dialog_id" + + payload = {"dialog_ids": [valid_dialog_id, invalid_dialog_id]} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 103, res + assert res["message"] == "Only owner of dialog authorized for this operation.", res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 1, res + + @pytest.mark.p3 + def test_delete_dialog_concurrent(self, WebApiAuth, add_dialogs_func): + _, dialog_ids = add_dialogs_func + + count = len(dialog_ids) + with ThreadPoolExecutor(max_workers=3) as executor: + futures = [executor.submit(delete_dialog, WebApiAuth, {"dialog_ids": [dialog_id]}) for dialog_id in dialog_ids] + + responses = [future.result() for future in as_completed(futures)] + + successful_deletions = sum(1 for response in responses if response["code"] == 0) + assert successful_deletions > 0, "No dialogs were successfully deleted" + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == count - successful_deletions, res + + @pytest.mark.p3 + def test_delete_dialog_idempotent(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + + payload = {"dialog_ids": [dialog_id]} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.p3 + def test_delete_large_batch_dialogs(self, WebApiAuth, add_document): + dataset_id, _ = add_document + + dialog_ids = batch_create_dialogs(WebApiAuth, 50, [dataset_id]) + assert len(dialog_ids) == 50, "Failed to create 50 dialogs" + + payload = {"dialog_ids": dialog_ids} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"] is True, res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 0, res + + @pytest.mark.p3 + def test_delete_dialog_with_special_characters(self, WebApiAuth): + payload = {"name": "Dialog with 特殊字符 and émojis 🤖", "description": "Test dialog with special characters", "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + create_res = create_dialog(WebApiAuth, payload) + assert create_res["code"] == 0, create_res + dialog_id = create_res["data"]["id"] + + delete_payload = {"dialog_ids": [dialog_id]} + res = delete_dialog(WebApiAuth, delete_payload) + assert res["code"] == 0, res + assert res["data"] is True, res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 0, res + + @pytest.mark.p3 + def test_delete_dialog_preserves_other_user_dialogs(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + + payload = {"dialog_ids": [dialog_id]} + res = delete_dialog(WebApiAuth, payload) + assert res["code"] == 0, res diff --git a/test/testcases/test_web_api/test_dialog_app/test_dialog_edge_cases.py b/test/testcases/test_web_api/test_dialog_app/test_dialog_edge_cases.py new file mode 100644 index 000000000..9536a1e35 --- /dev/null +++ b/test/testcases/test_web_api/test_dialog_app/test_dialog_edge_cases.py @@ -0,0 +1,205 @@ +# +# 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_dialog, delete_dialog, get_dialog, update_dialog + + +@pytest.mark.usefixtures("clear_dialogs") +class TestDialogEdgeCases: + @pytest.mark.p2 + def test_create_dialog_with_tavily_api_key(self, WebApiAuth): + """Test creating dialog with Tavily API key instead of knowledge base""" + payload = { + "name": "tavily_dialog", + "prompt_config": {"system": "You are a helpful assistant. Use this knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}], "tavily_api_key": "test_tavily_key"}, + } + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.skip + @pytest.mark.p2 + def test_create_dialog_with_different_embedding_models(self, WebApiAuth): + """Test creating dialog with knowledge bases that have different embedding models""" + # This test would require creating datasets with different embedding models + # For now, we'll test the error case with a mock scenario + payload = { + "name": "mixed_embedding_dialog", + "kb_ids": ["kb_with_model_a", "kb_with_model_b"], + "prompt_config": {"system": "You are a helpful assistant with knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + res = create_dialog(WebApiAuth, payload) + # This should fail due to different embedding models + assert res["code"] == 102, res + assert "Datasets use different embedding models" in res["message"], res + + @pytest.mark.p2 + def test_create_dialog_with_extremely_long_system_prompt(self, WebApiAuth): + """Test creating dialog with very long system prompt""" + long_prompt = "You are a helpful assistant. " * 1000 + payload = {"name": "long_prompt_dialog", "prompt_config": {"system": long_prompt, "parameters": []}} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.p2 + def test_create_dialog_with_unicode_characters(self, WebApiAuth): + """Test creating dialog with Unicode characters in various fields""" + payload = { + "name": "Unicode测试对话🤖", + "description": "测试Unicode字符支持 with émojis 🚀🌟", + "icon": "🤖", + "prompt_config": {"system": "你是一个有用的助手。You are helpful. Vous êtes utile. 🌍", "parameters": []}, + } + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["name"] == "Unicode测试对话🤖", res + assert res["data"]["description"] == "测试Unicode字符支持 with émojis 🚀🌟", res + + @pytest.mark.p2 + def test_create_dialog_with_extreme_parameter_values(self, WebApiAuth): + """Test creating dialog with extreme parameter values""" + payload = { + "name": "extreme_params_dialog", + "top_n": 0, + "top_k": 1, + "similarity_threshold": 0.0, + "vector_similarity_weight": 1.0, + "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}, + } + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["top_n"] == 0, res + assert res["data"]["top_k"] == 1, res + assert res["data"]["similarity_threshold"] == 0.0, res + assert res["data"]["vector_similarity_weight"] == 1.0, res + + @pytest.mark.p2 + def test_create_dialog_with_negative_parameter_values(self, WebApiAuth): + """Test creating dialog with negative parameter values""" + payload = { + "name": "negative_params_dialog", + "top_n": -1, + "top_k": -100, + "similarity_threshold": -0.5, + "vector_similarity_weight": -0.3, + "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}, + } + res = create_dialog(WebApiAuth, payload) + assert res["code"] in [0, 102], res + + @pytest.mark.p2 + def test_update_dialog_with_empty_kb_ids(self, WebApiAuth, add_dialog_func): + """Test updating dialog to remove all knowledge bases""" + dataset_id, dialog_id = add_dialog_func + payload = {"dialog_id": dialog_id, "kb_ids": [], "prompt_config": {"system": "You are a helpful assistant without knowledge.", "parameters": []}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["kb_ids"] == [], res + + @pytest.mark.p2 + def test_update_dialog_with_null_values(self, WebApiAuth, add_dialog_func): + """Test updating dialog with null/None values""" + dataset_id, dialog_id = add_dialog_func + payload = {"dialog_id": dialog_id, "description": None, "icon": None, "rerank_id": None, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.p3 + def test_dialog_with_complex_prompt_parameters(self, WebApiAuth, add_dataset_func): + """Test dialog with complex prompt parameter configurations""" + payload = { + "name": "complex_params_dialog", + "prompt_config": { + "system": "You are {role} assistant. Use {knowledge} and consider {context}. Optional: {optional_param}", + "parameters": [{"key": "role", "optional": False}, {"key": "knowledge", "optional": True}, {"key": "context", "optional": False}, {"key": "optional_param", "optional": True}], + }, + "kb_ids": [add_dataset_func], + } + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.p3 + def test_dialog_with_malformed_prompt_parameters(self, WebApiAuth): + """Test dialog with malformed prompt parameter configurations""" + payload = { + "name": "malformed_params_dialog", + "prompt_config": { + "system": "You are a helpful assistant.", + "parameters": [ + { + "key": "", + "optional": False, + }, + {"optional": True}, + { + "key": "valid_param", + }, + ], + }, + } + res = create_dialog(WebApiAuth, payload) + + assert res["code"] in [0, 102], res + + @pytest.mark.p3 + def test_dialog_operations_with_special_ids(self, WebApiAuth): + """Test dialog operations with special ID formats""" + special_ids = [ + "00000000-0000-0000-0000-000000000000", + "ffffffff-ffff-ffff-ffff-ffffffffffff", + "12345678-1234-1234-1234-123456789abc", + ] + + for special_id in special_ids: + res = get_dialog(WebApiAuth, {"dialog_id": special_id}) + assert res["code"] == 102, f"Should fail for ID: {special_id}" + + res = delete_dialog(WebApiAuth, {"dialog_ids": [special_id]}) + assert res["code"] == 103, f"Should fail for ID: {special_id}" + + @pytest.mark.p3 + def test_dialog_with_extremely_large_llm_settings(self, WebApiAuth): + """Test dialog with very large LLM settings""" + large_llm_setting = { + "model": "gpt-4", + "temperature": 0.7, + "max_tokens": 999999, + "custom_param_" + "x" * 1000: "large_value_" + "y" * 1000, + } + payload = {"name": "large_llm_settings_dialog", "llm_setting": large_llm_setting, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = create_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + + @pytest.mark.p3 + def test_concurrent_dialog_operations(self, WebApiAuth, add_dialog_func): + """Test concurrent operations on the same dialog""" + from concurrent.futures import ThreadPoolExecutor, as_completed + + _, dialog_id = add_dialog_func + + def update_operation(i): + payload = {"dialog_id": dialog_id, "name": f"concurrent_update_{i}", "prompt_config": {"system": f"You are assistant number {i}.", "parameters": []}} + return update_dialog(WebApiAuth, payload) + + with ThreadPoolExecutor(max_workers=5) as executor: + futures = [executor.submit(update_operation, i) for i in range(10)] + + responses = [future.result() for future in as_completed(futures)] + + successful_updates = sum(1 for response in responses if response["code"] == 0) + assert successful_updates > 0, "No updates succeeded" + + res = get_dialog(WebApiAuth, {"dialog_id": dialog_id}) + assert res["code"] == 0, res diff --git a/test/testcases/test_web_api/test_dialog_app/test_get_dialog.py b/test/testcases/test_web_api/test_dialog_app/test_get_dialog.py new file mode 100644 index 000000000..9208a8a06 --- /dev/null +++ b/test/testcases/test_web_api/test_dialog_app/test_get_dialog.py @@ -0,0 +1,177 @@ +# +# 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_dialog, get_dialog +from configs import INVALID_API_TOKEN +from libs.auth import RAGFlowWebApiAuth + + +@pytest.mark.usefixtures("clear_dialogs") +class TestAuthorization: + @pytest.mark.p1 + @pytest.mark.parametrize( + "invalid_auth, expected_code, expected_message", + [ + (None, 401, ""), + (RAGFlowWebApiAuth(INVALID_API_TOKEN), 401, ""), + ], + ids=["empty_auth", "invalid_api_token"], + ) + def test_auth_invalid(self, invalid_auth, expected_code, expected_message, add_dialog_func): + _, dialog_id = add_dialog_func + res = get_dialog(invalid_auth, {"dialog_id": dialog_id}) + assert res["code"] == expected_code, res + assert res["message"] == expected_message, res + + +class TestDialogGet: + @pytest.mark.p1 + def test_get_existing_dialog(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + res = get_dialog(WebApiAuth, {"dialog_id": dialog_id}) + assert res["code"] == 0, res + data = res["data"] + assert data["id"] == dialog_id, res + assert "name" in data, res + assert "description" in data, res + assert "kb_ids" in data, res + assert "kb_names" in data, res + assert "prompt_config" in data, res + assert "llm_setting" in data, res + assert "top_n" in data, res + assert "top_k" in data, res + assert "similarity_threshold" in data, res + assert "vector_similarity_weight" in data, res + + @pytest.mark.p1 + def test_get_dialog_with_kb_names(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + res = get_dialog(WebApiAuth, {"dialog_id": dialog_id}) + assert res["code"] == 0, res + data = res["data"] + assert isinstance(data["kb_ids"], list), res + assert isinstance(data["kb_names"], list), res + assert len(data["kb_ids"]) == len(data["kb_names"]), res + + @pytest.mark.p2 + def test_get_nonexistent_dialog(self, WebApiAuth): + fake_dialog_id = "nonexistent_dialog_id" + res = get_dialog(WebApiAuth, {"dialog_id": fake_dialog_id}) + assert res["code"] == 102, res + assert "Dialog not found" in res["message"], res + + @pytest.mark.p2 + def test_get_dialog_missing_id(self, WebApiAuth): + res = get_dialog(WebApiAuth, {}) + assert res["code"] == 100, res + assert res["message"] == "", res + + @pytest.mark.p2 + def test_get_dialog_empty_id(self, WebApiAuth): + res = get_dialog(WebApiAuth, {"dialog_id": ""}) + assert res["code"] == 102, res + + @pytest.mark.p2 + def test_get_dialog_invalid_id_format(self, WebApiAuth): + res = get_dialog(WebApiAuth, {"dialog_id": "invalid_format"}) + assert res["code"] == 102, res + + @pytest.mark.p3 + def test_get_dialog_data_structure(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + res = get_dialog(WebApiAuth, {"dialog_id": dialog_id}) + assert res["code"] == 0, res + data = res["data"] + + required_fields = [ + "id", + "name", + "description", + "kb_ids", + "kb_names", + "prompt_config", + "llm_setting", + "top_n", + "top_k", + "similarity_threshold", + "vector_similarity_weight", + "create_time", + "update_time", + ] + for field in required_fields: + assert field in data, f"Missing field: {field}" + + assert isinstance(data["id"], str), res + assert isinstance(data["name"], str), res + assert isinstance(data["kb_ids"], list), res + assert isinstance(data["kb_names"], list), res + assert isinstance(data["prompt_config"], dict), res + assert isinstance(data["top_n"], int), res + assert isinstance(data["top_k"], int), res + assert isinstance(data["similarity_threshold"], (int, float)), res + assert isinstance(data["vector_similarity_weight"], (int, float)), res + + @pytest.mark.p3 + def test_get_dialog_prompt_config_structure(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + res = get_dialog(WebApiAuth, {"dialog_id": dialog_id}) + assert res["code"] == 0, res + + prompt_config = res["data"]["prompt_config"] + assert "system" in prompt_config, res + assert "parameters" in prompt_config, res + assert isinstance(prompt_config["system"], str), res + assert isinstance(prompt_config["parameters"], list), res + + @pytest.mark.p3 + def test_get_dialog_with_multiple_kbs(self, WebApiAuth, add_dataset_func): + dataset_id1 = add_dataset_func + dataset_id2 = add_dataset_func + + payload = { + "name": "multi_kb_dialog", + "kb_ids": [dataset_id1, dataset_id2], + "prompt_config": {"system": "You are a helpful assistant with knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + create_res = create_dialog(WebApiAuth, payload) + assert create_res["code"] == 0, create_res + dialog_id = create_res["data"]["id"] + + res = get_dialog(WebApiAuth, {"dialog_id": dialog_id}) + assert res["code"] == 0, res + data = res["data"] + assert len(data["kb_ids"]) == 2, res + assert len(data["kb_names"]) == 2, res + assert dataset_id1 in data["kb_ids"], res + assert dataset_id2 in data["kb_ids"], res + + @pytest.mark.p3 + def test_get_dialog_with_invalid_kb(self, WebApiAuth): + payload = { + "name": "invalid_kb_dialog", + "kb_ids": ["invalid_kb_id"], + "prompt_config": {"system": "You are a helpful assistant with knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + create_res = create_dialog(WebApiAuth, payload) + assert create_res["code"] == 0, create_res + dialog_id = create_res["data"]["id"] + + res = get_dialog(WebApiAuth, {"dialog_id": dialog_id}) + assert res["code"] == 0, res + data = res["data"] + + assert len(data["kb_ids"]) == 0, res + assert len(data["kb_names"]) == 0, res diff --git a/test/testcases/test_web_api/test_dialog_app/test_list_dialogs.py b/test/testcases/test_web_api/test_dialog_app/test_list_dialogs.py new file mode 100644 index 000000000..5bdec2aa8 --- /dev/null +++ b/test/testcases/test_web_api/test_dialog_app/test_list_dialogs.py @@ -0,0 +1,210 @@ +# +# 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 batch_create_dialogs, create_dialog, list_dialogs +from configs import INVALID_API_TOKEN +from libs.auth import RAGFlowWebApiAuth + + +@pytest.mark.usefixtures("clear_dialogs") +class TestAuthorization: + @pytest.mark.p1 + @pytest.mark.parametrize( + "invalid_auth, expected_code, expected_message", + [ + (None, 401, ""), + (RAGFlowWebApiAuth(INVALID_API_TOKEN), 401, ""), + ], + ids=["empty_auth", "invalid_api_token"], + ) + def test_auth_invalid(self, invalid_auth, expected_code, expected_message): + res = list_dialogs(invalid_auth) + assert res["code"] == expected_code, res + assert res["message"] == expected_message, res + + +class TestDialogList: + @pytest.mark.p1 + @pytest.mark.usefixtures("add_dialogs_func") + def test_list_empty_dialogs(self, WebApiAuth): + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 5, res + + @pytest.mark.p1 + def test_list_multiple_dialogs(self, WebApiAuth, add_dialogs_func): + _, dialog_ids = add_dialogs_func + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 5, res + + returned_ids = [dialog["id"] for dialog in res["data"]] + for dialog_id in dialog_ids: + assert dialog_id in returned_ids, res + + @pytest.mark.p2 + @pytest.mark.usefixtures("add_dialogs_func") + def test_list_dialogs_data_structure(self, WebApiAuth): + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 5, res + + dialog = res["data"][0] + required_fields = [ + "id", + "name", + "description", + "kb_ids", + "kb_names", + "prompt_config", + "llm_setting", + "top_n", + "top_k", + "similarity_threshold", + "vector_similarity_weight", + "create_time", + "update_time", + ] + for field in required_fields: + assert field in dialog, f"Missing field: {field}" + + assert isinstance(dialog["id"], str), res + assert isinstance(dialog["name"], str), res + assert isinstance(dialog["kb_ids"], list), res + assert isinstance(dialog["kb_names"], list), res + assert isinstance(dialog["prompt_config"], dict), res + assert isinstance(dialog["top_n"], int), res + assert isinstance(dialog["top_k"], int), res + assert isinstance(dialog["similarity_threshold"], (int, float)), res + assert isinstance(dialog["vector_similarity_weight"], (int, float)), res + + @pytest.mark.p2 + @pytest.mark.usefixtures("add_dialogs_func") + def test_list_dialogs_with_kb_names(self, WebApiAuth): + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + + dialog = res["data"][0] + assert isinstance(dialog["kb_ids"], list), res + assert isinstance(dialog["kb_names"], list), res + assert len(dialog["kb_ids"]) == len(dialog["kb_names"]), res + + @pytest.mark.p2 + @pytest.mark.usefixtures("add_dialogs_func") + def test_list_dialogs_ordering(self, WebApiAuth): + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 5, res + + dialogs = res["data"] + for i in range(len(dialogs) - 1): + current_time = dialogs[i]["create_time"] + next_time = dialogs[i + 1]["create_time"] + assert current_time >= next_time, f"Dialogs not properly ordered: {current_time} should be >= {next_time}" + + @pytest.mark.p3 + @pytest.mark.usefixtures("clear_dialogs") + def test_list_dialogs_with_invalid_kb(self, WebApiAuth): + payload = { + "name": "invalid_kb_dialog", + "kb_ids": ["invalid_kb_id"], + "prompt_config": {"system": "You are a helpful assistant with knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + create_res = create_dialog(WebApiAuth, payload) + assert create_res["code"] == 0, create_res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 1, res + + dialog = res["data"][0] + + assert len(dialog["kb_ids"]) == 0, res + assert len(dialog["kb_names"]) == 0, res + + @pytest.mark.p3 + @pytest.mark.usefixtures("clear_dialogs") + def test_list_dialogs_with_multiple_kbs(self, WebApiAuth, add_dataset_func): + dataset_id1 = add_dataset_func + dataset_id2 = add_dataset_func + + payload = { + "name": "multi_kb_dialog", + "kb_ids": [dataset_id1, dataset_id2], + "prompt_config": {"system": "You are a helpful assistant with knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + create_res = create_dialog(WebApiAuth, payload) + assert create_res["code"] == 0, create_res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 1, res + + dialog = res["data"][0] + assert len(dialog["kb_ids"]) == 2, res + assert len(dialog["kb_names"]) == 2, res + assert dataset_id1 in dialog["kb_ids"], res + assert dataset_id2 in dialog["kb_ids"], res + + @pytest.mark.p3 + @pytest.mark.usefixtures("add_dialogs_func") + def test_list_dialogs_prompt_config_structure(self, WebApiAuth): + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + + dialog = res["data"][0] + prompt_config = dialog["prompt_config"] + assert "system" in prompt_config, res + assert "parameters" in prompt_config, res + assert isinstance(prompt_config["system"], str), res + assert isinstance(prompt_config["parameters"], list), res + + @pytest.mark.p3 + @pytest.mark.usefixtures("clear_dialogs") + def test_list_dialogs_performance(self, WebApiAuth, add_document): + dataset_id, _ = add_document + dialog_ids = batch_create_dialogs(WebApiAuth, 100, [dataset_id]) + assert len(dialog_ids) == 100, "Failed to create 100 dialogs" + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 100, res + + returned_ids = [dialog["id"] for dialog in res["data"]] + for dialog_id in dialog_ids: + assert dialog_id in returned_ids, f"Dialog {dialog_id} not found in list" + + @pytest.mark.p3 + @pytest.mark.usefixtures("clear_dialogs") + def test_list_dialogs_with_mixed_kb_states(self, WebApiAuth, add_dataset_func): + valid_dataset_id = add_dataset_func + + payload = { + "name": "mixed_kb_dialog", + "kb_ids": [valid_dataset_id, "invalid_kb_id"], + "prompt_config": {"system": "You are a helpful assistant with knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + create_res = create_dialog(WebApiAuth, payload) + assert create_res["code"] == 0, create_res + + res = list_dialogs(WebApiAuth) + assert res["code"] == 0, res + assert len(res["data"]) == 1, res + + dialog = res["data"][0] + assert len(dialog["kb_ids"]) == 1, res + assert dialog["kb_ids"][0] == valid_dataset_id, res + assert len(dialog["kb_names"]) == 1, res diff --git a/test/testcases/test_web_api/test_dialog_app/test_update_dialog.py b/test/testcases/test_web_api/test_dialog_app/test_update_dialog.py new file mode 100644 index 000000000..5949eefe8 --- /dev/null +++ b/test/testcases/test_web_api/test_dialog_app/test_update_dialog.py @@ -0,0 +1,170 @@ +# +# 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 update_dialog +from configs import INVALID_API_TOKEN +from libs.auth import RAGFlowWebApiAuth + + +@pytest.mark.usefixtures("clear_dialogs") +class TestAuthorization: + @pytest.mark.p1 + @pytest.mark.parametrize( + "invalid_auth, expected_code, expected_message", + [ + (None, 401, ""), + (RAGFlowWebApiAuth(INVALID_API_TOKEN), 401, ""), + ], + ids=["empty_auth", "invalid_api_token"], + ) + def test_auth_invalid(self, invalid_auth, expected_code, expected_message, add_dialog_func): + _, dialog_id = add_dialog_func + payload = {"dialog_id": dialog_id, "name": "updated_name", "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = update_dialog(invalid_auth, payload) + assert res["code"] == expected_code, res + assert res["message"] == expected_message, res + + +class TestDialogUpdate: + @pytest.mark.p1 + def test_update_name(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + new_name = "updated_dialog_name" + payload = {"dialog_id": dialog_id, "name": new_name, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["name"] == new_name, res + + @pytest.mark.p1 + def test_update_description(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + new_description = "Updated description" + payload = {"dialog_id": dialog_id, "description": new_description, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["description"] == new_description, res + + @pytest.mark.p1 + def test_update_prompt_config(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + new_prompt_config = {"system": "You are an updated helpful assistant with {param1}.", "parameters": [{"key": "param1", "optional": False}]} + payload = {"dialog_id": dialog_id, "prompt_config": new_prompt_config} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["prompt_config"]["system"] == new_prompt_config["system"], res + + @pytest.mark.p1 + def test_update_kb_ids(self, WebApiAuth, add_dialog_func, add_dataset_func): + _, dialog_id = add_dialog_func + new_dataset_id = add_dataset_func + payload = { + "dialog_id": dialog_id, + "kb_ids": [new_dataset_id], + "prompt_config": {"system": "You are a helpful assistant with knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}, + } + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert new_dataset_id in res["data"]["kb_ids"], res + + @pytest.mark.p1 + def test_update_llm_settings(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + new_llm_setting = {"model": "gpt-4", "temperature": 0.9, "max_tokens": 2000} + payload = {"dialog_id": dialog_id, "llm_setting": new_llm_setting, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["llm_setting"]["model"] == "gpt-4", res + assert res["data"]["llm_setting"]["temperature"] == 0.9, res + + @pytest.mark.p1 + def test_update_retrieval_settings(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + payload = { + "dialog_id": dialog_id, + "top_n": 15, + "top_k": 4096, + "similarity_threshold": 0.3, + "vector_similarity_weight": 0.7, + "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}, + } + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["top_n"] == 15, res + assert res["data"]["top_k"] == 4096, res + assert res["data"]["similarity_threshold"] == 0.3, res + assert res["data"]["vector_similarity_weight"] == 0.7, res + + @pytest.mark.p2 + def test_update_nonexistent_dialog(self, WebApiAuth): + fake_dialog_id = "nonexistent_dialog_id" + payload = {"dialog_id": fake_dialog_id, "name": "updated_name", "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 102, res + assert "Dialog not found" in res["message"], res + + @pytest.mark.p2 + def test_update_with_invalid_prompt_config(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + payload = {"dialog_id": dialog_id, "prompt_config": {"system": "You are a helpful assistant.", "parameters": [{"key": "unused_param", "optional": False}]}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 102, res + assert "Parameter 'unused_param' is not used" in res["message"], res + + @pytest.mark.p2 + def test_update_with_knowledge_but_no_kb(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + payload = {"dialog_id": dialog_id, "kb_ids": [], "prompt_config": {"system": "You are a helpful assistant with knowledge: {knowledge}", "parameters": [{"key": "knowledge", "optional": True}]}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 102, res + assert "Please remove `{knowledge}` in system prompt" in res["message"], res + + @pytest.mark.p2 + def test_update_icon(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + new_icon = "🚀" + payload = {"dialog_id": dialog_id, "icon": new_icon, "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["icon"] == new_icon, res + + @pytest.mark.p2 + def test_update_rerank_id(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + payload = {"dialog_id": dialog_id, "rerank_id": "test_rerank_model", "prompt_config": {"system": "You are a helpful assistant.", "parameters": []}} + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + assert res["data"]["rerank_id"] == "test_rerank_model", res + + @pytest.mark.p3 + def test_update_multiple_fields(self, WebApiAuth, add_dialog_func): + _, dialog_id = add_dialog_func + payload = { + "dialog_id": dialog_id, + "name": "multi_update_dialog", + "description": "Updated with multiple fields", + "icon": "🔄", + "top_n": 20, + "similarity_threshold": 0.4, + "prompt_config": {"system": "You are a multi-updated assistant.", "parameters": []}, + } + res = update_dialog(WebApiAuth, payload) + assert res["code"] == 0, res + data = res["data"] + assert data["name"] == "multi_update_dialog", res + assert data["description"] == "Updated with multiple fields", res + assert data["icon"] == "🔄", res + assert data["top_n"] == 20, res + assert data["similarity_threshold"] == 0.4, res