mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-04 03:25:30 +08:00
Compare commits
3 Commits
ca3bd2cf9f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f4a17863f | |||
| 4d3a3a97ef | |||
| ff1020ccfb |
@ -53,6 +53,8 @@ sql_command: list_services
|
||||
| alter_user_role
|
||||
| show_user_permission
|
||||
| show_version
|
||||
| grant_admin
|
||||
| revoke_admin
|
||||
|
||||
// meta command definition
|
||||
meta_command: "\\" meta_command_name [meta_args]
|
||||
@ -77,6 +79,7 @@ DROP: "DROP"i
|
||||
USER: "USER"i
|
||||
ALTER: "ALTER"i
|
||||
ACTIVE: "ACTIVE"i
|
||||
ADMIN: "ADMIN"i
|
||||
PASSWORD: "PASSWORD"i
|
||||
DATASETS: "DATASETS"i
|
||||
OF: "OF"i
|
||||
@ -123,6 +126,9 @@ revoke_permission: REVOKE action_list ON identifier FROM ROLE identifier ";"
|
||||
alter_user_role: ALTER USER quoted_string SET ROLE identifier ";"
|
||||
show_user_permission: SHOW USER PERMISSION quoted_string ";"
|
||||
|
||||
grant_admin: GRANT ADMIN quoted_string ";"
|
||||
revoke_admin: REVOKE ADMIN quoted_string ";"
|
||||
|
||||
show_version: SHOW VERSION ";"
|
||||
|
||||
action_list: identifier ("," identifier)*
|
||||
@ -249,6 +255,14 @@ class AdminTransformer(Transformer):
|
||||
def show_version(self, items):
|
||||
return {"type": "show_version"}
|
||||
|
||||
def grant_admin(self, items):
|
||||
user_name = items[2]
|
||||
return {"type": "grant_admin", "user_name": user_name}
|
||||
|
||||
def revoke_admin(self, items):
|
||||
user_name = items[2]
|
||||
return {"type": "revoke_admin", "user_name": user_name}
|
||||
|
||||
def action_list(self, items):
|
||||
return items
|
||||
|
||||
@ -286,6 +300,43 @@ def encode_to_base64(input_string):
|
||||
return base64_encoded.decode("utf-8")
|
||||
|
||||
|
||||
def show_help():
|
||||
"""Help info"""
|
||||
help_text = """
|
||||
Commands:
|
||||
LIST SERVICES
|
||||
SHOW SERVICE <service>
|
||||
STARTUP SERVICE <service>
|
||||
SHUTDOWN SERVICE <service>
|
||||
RESTART SERVICE <service>
|
||||
LIST USERS
|
||||
SHOW USER <user>
|
||||
DROP USER <user>
|
||||
CREATE USER <user> <password>
|
||||
ALTER USER PASSWORD <user> <new_password>
|
||||
ALTER USER ACTIVE <user> <on/off>
|
||||
LIST DATASETS OF <user>
|
||||
LIST AGENTS OF <user>
|
||||
CREATE ROLE <role>
|
||||
DROP ROLE <role>
|
||||
ALTER ROLE <role> SET DESCRIPTION <description>
|
||||
LIST ROLES
|
||||
SHOW ROLE <role>
|
||||
GRANT <action_list> ON <function> TO ROLE <role>
|
||||
REVOKE <action_list> ON <function> TO ROLE <role>
|
||||
ALTER USER <user> SET ROLE <role>
|
||||
SHOW USER PERMISSION <user>
|
||||
SHOW VERSION
|
||||
GRANT ADMIN <user>
|
||||
REVOKE ADMIN <user>
|
||||
|
||||
Meta Commands:
|
||||
\\?, \\h, \\help Show this help
|
||||
\\q, \\quit, \\exit Quit the CLI
|
||||
"""
|
||||
print(help_text)
|
||||
|
||||
|
||||
class AdminCLI(Cmd):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@ -566,6 +617,10 @@ class AdminCLI(Cmd):
|
||||
self._show_user_permission(command_dict)
|
||||
case "show_version":
|
||||
self._show_version(command_dict)
|
||||
case "grant_admin":
|
||||
self._grant_admin(command_dict)
|
||||
case "revoke_admin":
|
||||
self._revoke_admin(command_dict)
|
||||
case "meta":
|
||||
self._handle_meta_command(command_dict)
|
||||
case _:
|
||||
@ -698,6 +753,33 @@ class AdminCLI(Cmd):
|
||||
else:
|
||||
print(f"Unknown activate status: {activate_status}.")
|
||||
|
||||
|
||||
def _grant_admin(self, command):
|
||||
user_name_tree: Tree = command["user_name"]
|
||||
user_name: str = user_name_tree.children[0].strip("'\"")
|
||||
url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/admin"
|
||||
# print(f"Grant admin: {url}")
|
||||
# return
|
||||
response = self.session.put(url)
|
||||
res_json = response.json()
|
||||
if response.status_code == 200:
|
||||
print(res_json["message"])
|
||||
else:
|
||||
print(f"Fail to grant {user_name} admin authorization, code: {res_json['code']}, message: {res_json['message']}")
|
||||
|
||||
def _revoke_admin(self, command):
|
||||
user_name_tree: Tree = command["user_name"]
|
||||
user_name: str = user_name_tree.children[0].strip("'\"")
|
||||
url = f"http://{self.host}:{self.port}/api/v1/admin/users/{user_name}/admin"
|
||||
# print(f"Revoke admin: {url}")
|
||||
# return
|
||||
response = self.session.delete(url)
|
||||
res_json = response.json()
|
||||
if response.status_code == 200:
|
||||
print(res_json["message"])
|
||||
else:
|
||||
print(f"Fail to revoke {user_name} admin authorization, code: {res_json['code']}, message: {res_json['message']}")
|
||||
|
||||
def _handle_list_datasets(self, command):
|
||||
username_tree: Tree = command["user_name"]
|
||||
user_name: str = username_tree.children[0].strip("'\"")
|
||||
@ -873,36 +955,12 @@ class AdminCLI(Cmd):
|
||||
args = command.get("args", [])
|
||||
|
||||
if meta_command in ["?", "h", "help"]:
|
||||
self.show_help()
|
||||
show_help()
|
||||
elif meta_command in ["q", "quit", "exit"]:
|
||||
print("Goodbye!")
|
||||
else:
|
||||
print(f"Meta command '{meta_command}' with args {args}")
|
||||
|
||||
def show_help(self):
|
||||
"""Help info"""
|
||||
help_text = """
|
||||
Commands:
|
||||
LIST SERVICES
|
||||
SHOW SERVICE <service>
|
||||
STARTUP SERVICE <service>
|
||||
SHUTDOWN SERVICE <service>
|
||||
RESTART SERVICE <service>
|
||||
LIST USERS
|
||||
SHOW USER <user>
|
||||
DROP USER <user>
|
||||
CREATE USER <user> <password>
|
||||
ALTER USER PASSWORD <user> <new_password>
|
||||
ALTER USER ACTIVE <user> <on/off>
|
||||
LIST DATASETS OF <user>
|
||||
LIST AGENTS OF <user>
|
||||
|
||||
Meta Commands:
|
||||
\\?, \\h, \\help Show this help
|
||||
\\q, \\quit, \\exit Quit the CLI
|
||||
"""
|
||||
print(help_text)
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
|
||||
@ -158,6 +158,36 @@ def alter_user_activate_status(username):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users/<username>/admin', methods=['PUT'])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def grant_admin(username):
|
||||
try:
|
||||
if current_user.email == username:
|
||||
return error_response(f"can't grant current user: {username}", 409)
|
||||
msg = UserMgr.grant_admin(username)
|
||||
return success_response(None, msg)
|
||||
|
||||
except AdminException as e:
|
||||
return error_response(e.message, e.code)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/users/<username>/admin', methods=['DELETE'])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def revoke_admin(username):
|
||||
try:
|
||||
if current_user.email == username:
|
||||
return error_response(f"can't grant current user: {username}", 409)
|
||||
msg = UserMgr.revoke_admin(username)
|
||||
return success_response(None, msg)
|
||||
|
||||
except AdminException as e:
|
||||
return error_response(e.message, e.code)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/users/<username>', methods=['GET'])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
|
||||
@ -137,6 +137,38 @@ class UserMgr:
|
||||
UserService.update_user(usr.id, {"is_active": target_status})
|
||||
return f"Turn {_activate_status} user activate status successfully!"
|
||||
|
||||
@staticmethod
|
||||
def grant_admin(username: str):
|
||||
# use email to find user. check exist and unique.
|
||||
user_list = UserService.query_user_by_email(username)
|
||||
if not user_list:
|
||||
raise UserNotFoundError(username)
|
||||
elif len(user_list) > 1:
|
||||
raise AdminException(f"Exist more than 1 user: {username}!")
|
||||
# check activate status different from new
|
||||
usr = user_list[0]
|
||||
if usr.is_superuser:
|
||||
return f"{usr} is already superuser!"
|
||||
# update is_active
|
||||
UserService.update_user(usr.id, {"is_superuser": True})
|
||||
return "Grant successfully!"
|
||||
|
||||
@staticmethod
|
||||
def revoke_admin(username: str):
|
||||
# use email to find user. check exist and unique.
|
||||
user_list = UserService.query_user_by_email(username)
|
||||
if not user_list:
|
||||
raise UserNotFoundError(username)
|
||||
elif len(user_list) > 1:
|
||||
raise AdminException(f"Exist more than 1 user: {username}!")
|
||||
# check activate status different from new
|
||||
usr = user_list[0]
|
||||
if not usr.is_superuser:
|
||||
return f"{usr} isn't superuser, yet!"
|
||||
# update is_active
|
||||
UserService.update_user(usr.id, {"is_superuser": False})
|
||||
return "Revoke successfully!"
|
||||
|
||||
|
||||
class UserServiceMgr:
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ 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"
|
||||
MEMORY_API_URL = f"/{VERSION}/memories"
|
||||
MESSAGE_API_URL = f"/{VERSION}/messages"
|
||||
|
||||
|
||||
# KB APP
|
||||
@ -299,3 +300,76 @@ def get_memory_config(auth, memory_id:str):
|
||||
url = f"{HOST_ADDRESS}{MEMORY_API_URL}/{memory_id}/config"
|
||||
res = requests.get(url=url, headers=HEADERS, auth=auth)
|
||||
return res.json()
|
||||
|
||||
|
||||
def list_memory_message(auth, memory_id, params=None):
|
||||
url = f"{HOST_ADDRESS}{MEMORY_API_URL}/{memory_id}"
|
||||
if params:
|
||||
query_parts = []
|
||||
for key, value in params.items():
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
query_parts.append(f"{key}={item}")
|
||||
else:
|
||||
query_parts.append(f"{key}={value}")
|
||||
query_string = "&".join(query_parts)
|
||||
url = f"{url}?{query_string}"
|
||||
res = requests.get(url=url, headers=HEADERS, auth=auth)
|
||||
return res.json()
|
||||
|
||||
|
||||
def add_message(auth, payload=None):
|
||||
url = f"{HOST_ADDRESS}{MESSAGE_API_URL}"
|
||||
res = requests.post(url=url, headers=HEADERS, auth=auth, json=payload)
|
||||
return res.json()
|
||||
|
||||
|
||||
def forget_message(auth, memory_id: str, message_id: int):
|
||||
url = f"{HOST_ADDRESS}{MESSAGE_API_URL}/{memory_id}:{message_id}"
|
||||
res = requests.delete(url=url, headers=HEADERS, auth=auth)
|
||||
return res.json()
|
||||
|
||||
|
||||
def update_message_status(auth, memory_id: str, message_id: int, status: bool):
|
||||
url = f"{HOST_ADDRESS}{MESSAGE_API_URL}/{memory_id}:{message_id}"
|
||||
payload = {"status": status}
|
||||
res = requests.put(url=url, headers=HEADERS, auth=auth, json=payload)
|
||||
return res.json()
|
||||
|
||||
|
||||
def search_message(auth, params=None):
|
||||
url = f"{HOST_ADDRESS}{MESSAGE_API_URL}/search"
|
||||
if params:
|
||||
query_parts = []
|
||||
for key, value in params.items():
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
query_parts.append(f"{key}={item}")
|
||||
else:
|
||||
query_parts.append(f"{key}={value}")
|
||||
query_string = "&".join(query_parts)
|
||||
url = f"{url}?{query_string}"
|
||||
res = requests.get(url=url, headers=HEADERS, auth=auth)
|
||||
return res.json()
|
||||
|
||||
|
||||
def get_recent_message(auth, params=None):
|
||||
url = f"{HOST_ADDRESS}{MESSAGE_API_URL}"
|
||||
if params:
|
||||
query_parts = []
|
||||
for key, value in params.items():
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
query_parts.append(f"{key}={item}")
|
||||
else:
|
||||
query_parts.append(f"{key}={value}")
|
||||
query_string = "&".join(query_parts)
|
||||
url = f"{url}?{query_string}"
|
||||
res = requests.get(url=url, headers=HEADERS, auth=auth)
|
||||
return res.json()
|
||||
|
||||
|
||||
def get_message_content(auth, memory_id: str, message_id: int):
|
||||
url = f"{HOST_ADDRESS}{MESSAGE_API_URL}/{memory_id}:{message_id}/content"
|
||||
res = requests.get(url=url, headers=HEADERS, auth=auth)
|
||||
return res.json()
|
||||
|
||||
101
test/testcases/test_web_api/test_message_app/conftest.py
Normal file
101
test/testcases/test_web_api/test_message_app/conftest.py
Normal file
@ -0,0 +1,101 @@
|
||||
#
|
||||
# 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 time
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import random
|
||||
from test_web_api.common import create_memory, list_memory, add_message, delete_memory
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def add_memory_with_multiple_message_func(request, WebApiAuth):
|
||||
def cleanup():
|
||||
memory_list_res = list_memory(WebApiAuth)
|
||||
exist_memory_ids = [memory["id"] for memory in memory_list_res["data"]["memory_list"]]
|
||||
for _memory_id in exist_memory_ids:
|
||||
delete_memory(WebApiAuth, _memory_id)
|
||||
|
||||
request.addfinalizer(cleanup)
|
||||
|
||||
payload = {
|
||||
"name": "test_memory_0",
|
||||
"memory_type": ["raw"] + random.choices(["semantic", "episodic", "procedural"], k=random.randint(1, 3)),
|
||||
"embd_id": "BAAI/bge-small-en-v1.5@Builtin",
|
||||
"llm_id": "glm-4-flash@ZHIPU-AI"
|
||||
}
|
||||
res = create_memory(WebApiAuth, payload)
|
||||
memory_id = res["data"]["id"]
|
||||
agent_id = uuid.uuid4().hex
|
||||
message_payload = {
|
||||
"memory_id": [memory_id],
|
||||
"agent_id": agent_id,
|
||||
"session_id": uuid.uuid4().hex,
|
||||
"user_id": "",
|
||||
"user_input": "what is coriander?",
|
||||
"agent_response": """
|
||||
Coriander is a versatile herb with two main edible parts, and its name can refer to both:
|
||||
1. Leaves and Stems (often called Cilantro or Fresh Coriander): These are the fresh, green, fragrant leaves and tender stems of the plant Coriandrum sativum. They have a bright, citrusy, and sometimes pungent flavor. Cilantro is widely used as a garnish or key ingredient in cuisines like Mexican, Indian, Thai, and Middle Eastern.
|
||||
2. Seeds (called Coriander Seeds): These are the dried, golden-brown seeds of the same plant. When ground, they become coriander powder. The seeds have a warm, nutty, floral, and slightly citrusy taste, completely different from the fresh leaves. They are a fundamental spice in curries, stews, pickles, and baking.
|
||||
Key Point of Confusion: The naming differs by region. In North America, "coriander" typically refers to the seeds, while "cilantro" refers to the fresh leaves. In the UK, Europe, and many other parts of the world, "coriander" refers to the fresh herb, and the seeds are called "coriander seeds."
|
||||
"""
|
||||
}
|
||||
add_message(WebApiAuth, message_payload)
|
||||
request.cls.memory_id = memory_id
|
||||
request.cls.agent_id = agent_id
|
||||
return memory_id
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def add_memory_with_5_raw_message_func(request, WebApiAuth):
|
||||
def cleanup():
|
||||
memory_list_res = list_memory(WebApiAuth)
|
||||
exist_memory_ids = [memory["id"] for memory in memory_list_res["data"]["memory_list"]]
|
||||
for _memory_id in exist_memory_ids:
|
||||
delete_memory(WebApiAuth, _memory_id)
|
||||
|
||||
request.addfinalizer(cleanup)
|
||||
|
||||
payload = {
|
||||
"name": "test_memory_1",
|
||||
"memory_type": ["raw"],
|
||||
"embd_id": "BAAI/bge-small-en-v1.5@Builtin",
|
||||
"llm_id": "glm-4-flash@ZHIPU-AI"
|
||||
}
|
||||
res = create_memory(WebApiAuth, payload)
|
||||
memory_id = res["data"]["id"]
|
||||
agent_ids = [uuid.uuid4().hex for _ in range(2)]
|
||||
session_ids = [uuid.uuid4().hex for _ in range(5)]
|
||||
for i in range(5):
|
||||
message_payload = {
|
||||
"memory_id": [memory_id],
|
||||
"agent_id": agent_ids[i % 2],
|
||||
"session_id": session_ids[i],
|
||||
"user_id": "",
|
||||
"user_input": "what is coriander?",
|
||||
"agent_response": """
|
||||
Coriander is a versatile herb with two main edible parts, and its name can refer to both:
|
||||
1. Leaves and Stems (often called Cilantro or Fresh Coriander): These are the fresh, green, fragrant leaves and tender stems of the plant Coriandrum sativum. They have a bright, citrusy, and sometimes pungent flavor. Cilantro is widely used as a garnish or key ingredient in cuisines like Mexican, Indian, Thai, and Middle Eastern.
|
||||
2. Seeds (called Coriander Seeds): These are the dried, golden-brown seeds of the same plant. When ground, they become coriander powder. The seeds have a warm, nutty, floral, and slightly citrusy taste, completely different from the fresh leaves. They are a fundamental spice in curries, stews, pickles, and baking.
|
||||
Key Point of Confusion: The naming differs by region. In North America, "coriander" typically refers to the seeds, while "cilantro" refers to the fresh leaves. In the UK, Europe, and many other parts of the world, "coriander" refers to the fresh herb, and the seeds are called "coriander seeds."
|
||||
"""
|
||||
}
|
||||
add_message(WebApiAuth, message_payload)
|
||||
request.cls.memory_id = memory_id
|
||||
request.cls.agent_ids = agent_ids
|
||||
request.cls.session_ids = session_ids
|
||||
time.sleep(2) # make sure refresh to index before search
|
||||
return memory_id
|
||||
@ -0,0 +1,68 @@
|
||||
#
|
||||
# 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 random
|
||||
|
||||
import pytest
|
||||
from test_web_api.common import get_recent_message
|
||||
from configs import INVALID_API_TOKEN
|
||||
from libs.auth import RAGFlowWebApiAuth
|
||||
|
||||
|
||||
class TestAuthorization:
|
||||
@pytest.mark.p1
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_auth, expected_code, expected_message",
|
||||
[
|
||||
(None, 401, "<Unauthorized '401: Unauthorized'>"),
|
||||
(RAGFlowWebApiAuth(INVALID_API_TOKEN), 401, "<Unauthorized '401: Unauthorized'>"),
|
||||
],
|
||||
)
|
||||
def test_auth_invalid(self, invalid_auth, expected_code, expected_message):
|
||||
res = get_recent_message(invalid_auth)
|
||||
assert res["code"] == expected_code, res
|
||||
assert res["message"] == expected_message, res
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("add_memory_with_5_raw_message_func")
|
||||
class TestGetRecentMessage:
|
||||
|
||||
@pytest.mark.p1
|
||||
def test_get_recent_messages(self, WebApiAuth):
|
||||
memory_id = self.memory_id
|
||||
res = get_recent_message(WebApiAuth, params={"memory_id": memory_id})
|
||||
assert res["code"] == 0, res
|
||||
assert len(res["data"]) == 5, res
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_filter_recent_messages_by_agent(self, WebApiAuth):
|
||||
memory_id = self.memory_id
|
||||
agent_ids = self.agent_ids
|
||||
agent_id = random.choice(agent_ids)
|
||||
res = get_recent_message(WebApiAuth, params={"agent_id": agent_id, "memory_id": memory_id})
|
||||
assert res["code"] == 0, res
|
||||
for message in res["data"]:
|
||||
assert message["agent_id"] == agent_id, message
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_filter_recent_messages_by_session(self, WebApiAuth):
|
||||
memory_id = self.memory_id
|
||||
session_ids = self.session_ids
|
||||
session_id = random.choice(session_ids)
|
||||
res = get_recent_message(WebApiAuth, params={"session_id": session_id, "memory_id": memory_id})
|
||||
assert res["code"] == 0, res
|
||||
for message in res["data"]:
|
||||
assert message["session_id"] == session_id, message
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
#
|
||||
# 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 os
|
||||
import random
|
||||
|
||||
import pytest
|
||||
from test_web_api.common import list_memory_message
|
||||
from configs import INVALID_API_TOKEN
|
||||
from libs.auth import RAGFlowWebApiAuth
|
||||
|
||||
|
||||
class TestAuthorization:
|
||||
@pytest.mark.p1
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_auth, expected_code, expected_message",
|
||||
[
|
||||
(None, 401, "<Unauthorized '401: Unauthorized'>"),
|
||||
(RAGFlowWebApiAuth(INVALID_API_TOKEN), 401, "<Unauthorized '401: Unauthorized'>"),
|
||||
],
|
||||
)
|
||||
def test_auth_invalid(self, invalid_auth, expected_code, expected_message):
|
||||
res = list_memory_message(invalid_auth, "")
|
||||
assert res["code"] == expected_code, res
|
||||
assert res["message"] == expected_message, res
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("add_memory_with_5_raw_message_func")
|
||||
class TestMessageList:
|
||||
|
||||
@pytest.mark.p1
|
||||
def test_params_unset(self, WebApiAuth):
|
||||
memory_id = self.memory_id
|
||||
res = list_memory_message(WebApiAuth, memory_id, params=None)
|
||||
assert res["code"] == 0, res
|
||||
assert len(res["data"]["messages"]["message_list"]) == 5, res
|
||||
|
||||
@pytest.mark.p1
|
||||
def test_params_empty(self, WebApiAuth):
|
||||
memory_id = self.memory_id
|
||||
res = list_memory_message(WebApiAuth, memory_id, params={})
|
||||
assert res["code"] == 0, res
|
||||
assert len(res["data"]["messages"]["message_list"]) == 5, res
|
||||
|
||||
@pytest.mark.p1
|
||||
@pytest.mark.parametrize(
|
||||
"params, expected_page_size",
|
||||
[
|
||||
({"page": 1, "page_size": 10}, 5),
|
||||
({"page": 2, "page_size": 10}, 0),
|
||||
({"page": 1, "page_size": 2}, 2),
|
||||
({"page": 3, "page_size": 2}, 1),
|
||||
({"page": 5, "page_size": 10}, 0),
|
||||
],
|
||||
ids=["normal_first_page", "beyond_max_page", "normal_last_partial_page", "normal_middle_page",
|
||||
"full_data_single_page"],
|
||||
)
|
||||
def test_page_size(self, WebApiAuth, params, expected_page_size):
|
||||
# have added 5 messages in fixture
|
||||
memory_id = self.memory_id
|
||||
res = list_memory_message(WebApiAuth, memory_id, params=params)
|
||||
assert res["code"] == 0, res
|
||||
assert len(res["data"]["messages"]["message_list"]) == expected_page_size, res
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_filter_agent_id(self, WebApiAuth):
|
||||
memory_id = self.memory_id
|
||||
agent_ids = self.agent_ids
|
||||
agent_id = random.choice(agent_ids)
|
||||
res = list_memory_message(WebApiAuth, memory_id, params={"agent_id": agent_id})
|
||||
assert res["code"] == 0, res
|
||||
for message in res["data"]["messages"]["message_list"]:
|
||||
assert message["agent_id"] == agent_id, message
|
||||
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.skipif(os.getenv("DOC_ENGINE") == "infinity", reason="Not support.")
|
||||
def test_search_keyword(self, WebApiAuth):
|
||||
memory_id = self.memory_id
|
||||
session_ids = self.session_ids
|
||||
session_id = random.choice(session_ids)
|
||||
slice_start = random.randint(0, len(session_id) - 2)
|
||||
slice_end = random.randint(slice_start + 1, len(session_id) - 1)
|
||||
keyword = session_id[slice_start:slice_end]
|
||||
res = list_memory_message(WebApiAuth, memory_id, params={"keywords": keyword})
|
||||
assert res["code"] == 0, res
|
||||
assert len(res["data"]["messages"]["message_list"]) > 0, res
|
||||
for message in res["data"]["messages"]["message_list"]:
|
||||
assert keyword in message["session_id"], message
|
||||
Reference in New Issue
Block a user