mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 15:31:05 +08:00
Remove unused API (#14046)
### What problem does this PR solve? 1. Remove unused token related API 2. Fix typo ### Type of change - [x] Refactoring --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -247,7 +247,7 @@ jobs:
|
||||
echo "Waiting for service to be available... (last exit code: $?)"
|
||||
sleep 5
|
||||
done
|
||||
source .venv/bin/activate && set -o pipefail; DOC_ENGINE=infinity pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_web_api/test_api_app test/testcases/test_web_api/test_chunk_feedback 2>&1 | tee infinity_web_api_test.log
|
||||
source .venv/bin/activate && set -o pipefail; DOC_ENGINE=infinity pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_web_api/test_chunk_feedback 2>&1 | tee infinity_web_api_test.log
|
||||
|
||||
- name: Run http api tests against Infinity
|
||||
run: |
|
||||
|
||||
@@ -15,73 +15,11 @@
|
||||
#
|
||||
from datetime import datetime, timedelta
|
||||
from quart import request
|
||||
from api.db.db_models import APIToken
|
||||
from api.db.services.api_service import APITokenService, API4ConversationService
|
||||
from api.db.services.api_service import API4ConversationService
|
||||
from api.db.services.user_service import UserTenantService
|
||||
from api.utils.api_utils import generate_confirmation_token, get_data_error_result, get_json_result, get_request_json, server_error_response, validate_request
|
||||
from common.time_utils import current_timestamp, datetime_format
|
||||
from api.utils.api_utils import get_data_error_result, get_json_result, server_error_response
|
||||
from api.apps import login_required, current_user
|
||||
|
||||
|
||||
@manager.route('/new_token', methods=['POST']) # noqa: F821
|
||||
@login_required
|
||||
async def new_token():
|
||||
req = await get_request_json()
|
||||
try:
|
||||
tenants = UserTenantService.query(user_id=current_user.id)
|
||||
if not tenants:
|
||||
return get_data_error_result(message="Tenant not found!")
|
||||
|
||||
tenant_id = tenants[0].tenant_id
|
||||
obj = {"tenant_id": tenant_id, "token": generate_confirmation_token(),
|
||||
"create_time": current_timestamp(),
|
||||
"create_date": datetime_format(datetime.now()),
|
||||
"update_time": None,
|
||||
"update_date": None
|
||||
}
|
||||
if req.get("canvas_id"):
|
||||
obj["dialog_id"] = req["canvas_id"]
|
||||
obj["source"] = "agent"
|
||||
else:
|
||||
obj["dialog_id"] = req["dialog_id"]
|
||||
|
||||
if not APITokenService.save(**obj):
|
||||
return get_data_error_result(message="Fail to new a dialog!")
|
||||
|
||||
return get_json_result(data=obj)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/token_list', methods=['GET']) # noqa: F821
|
||||
@login_required
|
||||
def token_list():
|
||||
try:
|
||||
tenants = UserTenantService.query(user_id=current_user.id)
|
||||
if not tenants:
|
||||
return get_data_error_result(message="Tenant not found!")
|
||||
|
||||
id = request.args["dialog_id"] if "dialog_id" in request.args else request.args["canvas_id"]
|
||||
objs = APITokenService.query(tenant_id=tenants[0].tenant_id, dialog_id=id)
|
||||
return get_json_result(data=[o.to_dict() for o in objs])
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/rm', methods=['POST']) # noqa: F821
|
||||
@validate_request("tokens", "tenant_id")
|
||||
@login_required
|
||||
async def rm():
|
||||
req = await get_request_json()
|
||||
try:
|
||||
for token in req["tokens"]:
|
||||
APITokenService.filter_delete(
|
||||
[APIToken.tenant_id == req["tenant_id"], APIToken.token == token])
|
||||
return get_json_result(data=True)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/stats', methods=['GET']) # noqa: F821
|
||||
@login_required
|
||||
def stats():
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
#
|
||||
# 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 test_common import api_new_token, api_rm_token, api_stats, api_token_list, batch_create_chats
|
||||
from configs import INVALID_API_TOKEN
|
||||
from libs.auth import RAGFlowWebApiAuth
|
||||
|
||||
|
||||
INVALID_AUTH_CASES = [
|
||||
(None, 401, "Unauthorized"),
|
||||
(RAGFlowWebApiAuth(INVALID_API_TOKEN), 401, "Unauthorized"),
|
||||
]
|
||||
|
||||
|
||||
class TestAuthorization:
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.parametrize("invalid_auth, expected_code, expected_fragment", INVALID_AUTH_CASES)
|
||||
def test_auth_invalid_new_token(self, invalid_auth, expected_code, expected_fragment):
|
||||
res = api_new_token(invalid_auth, {"dialog_id": "dummy_dialog_id"})
|
||||
assert res["code"] == expected_code, res
|
||||
assert expected_fragment in res["message"], res
|
||||
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.parametrize("invalid_auth, expected_code, expected_fragment", INVALID_AUTH_CASES)
|
||||
def test_auth_invalid_token_list(self, invalid_auth, expected_code, expected_fragment):
|
||||
res = api_token_list(invalid_auth, {"dialog_id": "dummy_dialog_id"})
|
||||
assert res["code"] == expected_code, res
|
||||
assert expected_fragment in res["message"], res
|
||||
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.parametrize("invalid_auth, expected_code, expected_fragment", INVALID_AUTH_CASES)
|
||||
def test_auth_invalid_rm(self, invalid_auth, expected_code, expected_fragment):
|
||||
res = api_rm_token(invalid_auth, {"tokens": ["dummy_token"], "tenant_id": "dummy_tenant"})
|
||||
assert res["code"] == expected_code, res
|
||||
assert expected_fragment in res["message"], res
|
||||
|
||||
@pytest.mark.p2
|
||||
@pytest.mark.parametrize("invalid_auth, expected_code, expected_fragment", INVALID_AUTH_CASES)
|
||||
def test_auth_invalid_stats(self, invalid_auth, expected_code, expected_fragment):
|
||||
res = api_stats(invalid_auth)
|
||||
assert res["code"] == expected_code, res
|
||||
assert expected_fragment in res["message"], res
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("clear_chats")
|
||||
class TestApiTokens:
|
||||
@pytest.mark.p2
|
||||
def test_token_lifecycle(self, WebApiAuth):
|
||||
chat_id = batch_create_chats(WebApiAuth, 1)[0]
|
||||
create_res = api_new_token(WebApiAuth, {"dialog_id": chat_id})
|
||||
assert create_res["code"] == 0, create_res
|
||||
token = create_res["data"]["token"]
|
||||
tenant_id = create_res["data"]["tenant_id"]
|
||||
|
||||
list_res = api_token_list(WebApiAuth, {"dialog_id": chat_id})
|
||||
assert list_res["code"] == 0, list_res
|
||||
assert any(item["token"] == token for item in list_res["data"]), list_res
|
||||
|
||||
rm_res = api_rm_token(WebApiAuth, {"tokens": [token], "tenant_id": tenant_id})
|
||||
assert rm_res["code"] == 0, rm_res
|
||||
assert rm_res["data"] is True, rm_res
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_stats_basic(self, WebApiAuth):
|
||||
res = api_stats(WebApiAuth)
|
||||
assert res["code"] == 0, res
|
||||
for key in ["pv", "uv", "speed", "tokens", "round", "thumb_up"]:
|
||||
assert key in res["data"], res
|
||||
|
||||
@pytest.mark.p3
|
||||
def test_rm_missing_tokens(self, WebApiAuth):
|
||||
res = api_rm_token(WebApiAuth, {"tenant_id": "dummy_tenant"})
|
||||
assert res["code"] == 101, res
|
||||
assert "required argument are missing" in res["message"], res
|
||||
@@ -1,247 +0,0 @@
|
||||
#
|
||||
# Copyright 2026 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 asyncio
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from types import ModuleType, SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class _DummyManager:
|
||||
def route(self, *_args, **_kwargs):
|
||||
def decorator(func):
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class _ExprField:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.name, other)
|
||||
|
||||
|
||||
class _DummyAPITokenModel:
|
||||
tenant_id = _ExprField("tenant_id")
|
||||
token = _ExprField("token")
|
||||
|
||||
|
||||
def _run(coro):
|
||||
return asyncio.run(coro)
|
||||
|
||||
|
||||
def _load_api_app(monkeypatch):
|
||||
repo_root = Path(__file__).resolve().parents[4]
|
||||
|
||||
quart_mod = ModuleType("quart")
|
||||
quart_mod.request = SimpleNamespace(args={})
|
||||
monkeypatch.setitem(sys.modules, "quart", quart_mod)
|
||||
|
||||
apps_mod = ModuleType("api.apps")
|
||||
apps_mod.__path__ = [str(repo_root / "api" / "apps")]
|
||||
apps_mod.login_required = lambda fn: fn
|
||||
apps_mod.current_user = SimpleNamespace(id="user-1")
|
||||
monkeypatch.setitem(sys.modules, "api.apps", apps_mod)
|
||||
|
||||
api_utils_mod = ModuleType("api.utils.api_utils")
|
||||
|
||||
async def _get_request_json():
|
||||
return {}
|
||||
|
||||
api_utils_mod.generate_confirmation_token = lambda: "token-123"
|
||||
api_utils_mod.get_request_json = _get_request_json
|
||||
api_utils_mod.get_json_result = lambda data=None, message="", code=0: {
|
||||
"code": code,
|
||||
"message": message,
|
||||
"data": data,
|
||||
}
|
||||
api_utils_mod.get_data_error_result = lambda message="", code=400, data=None: {
|
||||
"code": code,
|
||||
"message": message,
|
||||
"data": data,
|
||||
}
|
||||
api_utils_mod.server_error_response = lambda exc: {
|
||||
"code": 500,
|
||||
"message": str(exc),
|
||||
"data": None,
|
||||
}
|
||||
api_utils_mod.validate_request = lambda *_args, **_kwargs: (lambda fn: fn)
|
||||
monkeypatch.setitem(sys.modules, "api.utils.api_utils", api_utils_mod)
|
||||
|
||||
api_service_mod = ModuleType("api.db.services.api_service")
|
||||
|
||||
class _StubAPITokenService:
|
||||
@staticmethod
|
||||
def save(**_kwargs):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def query(**_kwargs):
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def filter_delete(_conds):
|
||||
return True
|
||||
|
||||
class _StubAPI4ConversationService:
|
||||
@staticmethod
|
||||
def stats(*_args, **_kwargs):
|
||||
return []
|
||||
|
||||
api_service_mod.APITokenService = _StubAPITokenService
|
||||
api_service_mod.API4ConversationService = _StubAPI4ConversationService
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.api_service", api_service_mod)
|
||||
|
||||
user_service_mod = ModuleType("api.db.services.user_service")
|
||||
|
||||
class _StubUserTenantService:
|
||||
@staticmethod
|
||||
def query(**_kwargs):
|
||||
return [SimpleNamespace(tenant_id="tenant-1")]
|
||||
|
||||
user_service_mod.UserTenantService = _StubUserTenantService
|
||||
monkeypatch.setitem(sys.modules, "api.db.services.user_service", user_service_mod)
|
||||
|
||||
db_models_mod = ModuleType("api.db.db_models")
|
||||
db_models_mod.APIToken = _DummyAPITokenModel
|
||||
monkeypatch.setitem(sys.modules, "api.db.db_models", db_models_mod)
|
||||
|
||||
time_utils_mod = ModuleType("common.time_utils")
|
||||
time_utils_mod.current_timestamp = lambda: 123
|
||||
time_utils_mod.datetime_format = lambda _dt: "2026-01-01 00:00:00"
|
||||
monkeypatch.setitem(sys.modules, "common.time_utils", time_utils_mod)
|
||||
|
||||
module_path = repo_root / "api" / "apps" / "api_app.py"
|
||||
spec = importlib.util.spec_from_file_location("test_api_tokens_unit_module", module_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
module.manager = _DummyManager()
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_new_token_branches_and_error_paths(monkeypatch):
|
||||
module = _load_api_app(monkeypatch)
|
||||
|
||||
async def req_canvas():
|
||||
return {"canvas_id": "canvas-1"}
|
||||
|
||||
monkeypatch.setattr(module, "get_request_json", req_canvas)
|
||||
monkeypatch.setattr(module.UserTenantService, "query", lambda **_kwargs: [])
|
||||
res = _run(module.new_token())
|
||||
assert res["message"] == "Tenant not found!"
|
||||
|
||||
monkeypatch.setattr(module.UserTenantService, "query", lambda **_kwargs: [SimpleNamespace(tenant_id="tenant-1")])
|
||||
monkeypatch.setattr(module.APITokenService, "save", lambda **_kwargs: True)
|
||||
res = _run(module.new_token())
|
||||
assert res["code"] == 0
|
||||
assert res["data"]["tenant_id"] == "tenant-1"
|
||||
assert res["data"]["dialog_id"] == "canvas-1"
|
||||
assert res["data"]["source"] == "agent"
|
||||
|
||||
monkeypatch.setattr(module.APITokenService, "save", lambda **_kwargs: False)
|
||||
res = _run(module.new_token())
|
||||
assert res["message"] == "Fail to new a dialog!"
|
||||
|
||||
monkeypatch.setattr(module.UserTenantService, "query", lambda **_kwargs: (_ for _ in ()).throw(RuntimeError("query failed")))
|
||||
res = _run(module.new_token())
|
||||
assert res["code"] == 500
|
||||
assert "query failed" in res["message"]
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_token_list_tenant_guard_and_exception(monkeypatch):
|
||||
module = _load_api_app(monkeypatch)
|
||||
|
||||
monkeypatch.setattr(module.UserTenantService, "query", lambda **_kwargs: [])
|
||||
monkeypatch.setattr(module, "request", SimpleNamespace(args={"dialog_id": "d1"}))
|
||||
res = module.token_list()
|
||||
assert res["message"] == "Tenant not found!"
|
||||
|
||||
monkeypatch.setattr(module.UserTenantService, "query", lambda **_kwargs: [SimpleNamespace(tenant_id="tenant-1")])
|
||||
monkeypatch.setattr(module, "request", SimpleNamespace(args={}))
|
||||
res = module.token_list()
|
||||
assert res["code"] == 500
|
||||
assert "canvas_id" in res["message"]
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_rm_exception_path(monkeypatch):
|
||||
module = _load_api_app(monkeypatch)
|
||||
|
||||
async def req_rm():
|
||||
return {"tokens": ["tok-1"], "tenant_id": "tenant-1"}
|
||||
|
||||
monkeypatch.setattr(module, "get_request_json", req_rm)
|
||||
monkeypatch.setattr(
|
||||
module.APITokenService,
|
||||
"filter_delete",
|
||||
lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("delete failed")),
|
||||
)
|
||||
|
||||
res = _run(module.rm())
|
||||
assert res["code"] == 500
|
||||
assert "delete failed" in res["message"]
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_stats_aggregation_and_error_paths(monkeypatch):
|
||||
module = _load_api_app(monkeypatch)
|
||||
|
||||
monkeypatch.setattr(module.UserTenantService, "query", lambda **_kwargs: [])
|
||||
monkeypatch.setattr(module, "request", SimpleNamespace(args={}))
|
||||
res = module.stats()
|
||||
assert res["message"] == "Tenant not found!"
|
||||
|
||||
monkeypatch.setattr(module.UserTenantService, "query", lambda **_kwargs: [SimpleNamespace(tenant_id="tenant-1")])
|
||||
monkeypatch.setattr(module, "request", SimpleNamespace(args={"canvas_id": "canvas-1"}))
|
||||
monkeypatch.setattr(
|
||||
module.API4ConversationService,
|
||||
"stats",
|
||||
lambda *_args, **_kwargs: [
|
||||
{
|
||||
"dt": "2026-01-01",
|
||||
"pv": 3,
|
||||
"uv": 2,
|
||||
"tokens": 100,
|
||||
"duration": 9.9,
|
||||
"round": 1,
|
||||
"thumb_up": 0,
|
||||
}
|
||||
],
|
||||
)
|
||||
res = module.stats()
|
||||
assert res["code"] == 0
|
||||
assert res["data"]["pv"] == [("2026-01-01", 3)]
|
||||
assert res["data"]["uv"] == [("2026-01-01", 2)]
|
||||
assert res["data"]["round"] == [("2026-01-01", 1)]
|
||||
assert res["data"]["thumb_up"] == [("2026-01-01", 0)]
|
||||
assert res["data"]["tokens"] == [("2026-01-01", 0.1)]
|
||||
assert res["data"]["speed"] == [("2026-01-01", 10.0)]
|
||||
|
||||
monkeypatch.setattr(
|
||||
module.API4ConversationService,
|
||||
"stats",
|
||||
lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("stats failed")),
|
||||
)
|
||||
res = module.stats()
|
||||
assert res["code"] == 500
|
||||
assert "stats failed" in res["message"]
|
||||
@@ -68,25 +68,6 @@ def _log_http_debug(method, url, req_id, payload, status, text, resp_json, elaps
|
||||
print(f"[HTTP DEBUG] response_text={text}")
|
||||
print(f"[HTTP DEBUG] response_json={json.dumps(resp_json, default=str) if resp_json is not None else None}")
|
||||
|
||||
|
||||
# API APP
|
||||
def api_new_token(auth, payload=None, *, headers=HEADERS, data=None):
|
||||
if payload is None:
|
||||
payload = {}
|
||||
res = requests.post(url=f"{HOST_ADDRESS}{API_APP_URL}/new_token", headers=headers, auth=auth, json=payload, data=data)
|
||||
return res.json()
|
||||
|
||||
|
||||
def api_token_list(auth, params=None, *, headers=HEADERS):
|
||||
res = requests.get(url=f"{HOST_ADDRESS}{API_APP_URL}/token_list", headers=headers, auth=auth, params=params)
|
||||
return res.json()
|
||||
|
||||
|
||||
def api_rm_token(auth, payload=None, *, headers=HEADERS, data=None):
|
||||
res = requests.post(url=f"{HOST_ADDRESS}{API_APP_URL}/rm", headers=headers, auth=auth, json=payload, data=data)
|
||||
return res.json()
|
||||
|
||||
|
||||
def api_stats(auth, params=None, *, headers=HEADERS):
|
||||
res = requests.get(url=f"{HOST_ADDRESS}{API_APP_URL}/stats", headers=headers, auth=auth, params=params)
|
||||
return res.json()
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useParams, useSearchParams } from 'react-router';
|
||||
import { LogTabs } from './dataset-common';
|
||||
import { IFileLogList, IOverviewTotal } from './interface';
|
||||
|
||||
const useFetchOverviewTital = () => {
|
||||
const useFetchOverviewTotal = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const { id } = useParams();
|
||||
const knowledgeBaseId = searchParams.get('id') || id;
|
||||
@@ -95,4 +95,4 @@ const useFetchFileLogList = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export { useFetchFileLogList, useFetchOverviewTital };
|
||||
export { useFetchFileLogList, useFetchOverviewTotal };
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { RunningStatus } from '../dataset/constant';
|
||||
import { LogTabs } from './dataset-common';
|
||||
import { DatasetFilter } from './dataset-filter';
|
||||
import { useFetchFileLogList, useFetchOverviewTital } from './hook';
|
||||
import { useFetchFileLogList, useFetchOverviewTotal } from './hook';
|
||||
import { DocumentLog, IFileLogItem } from './interface';
|
||||
import FileLogsTable from './overview-table';
|
||||
|
||||
@@ -133,7 +133,7 @@ const FileLogsPage: FC = () => {
|
||||
failed: 0,
|
||||
},
|
||||
});
|
||||
const { data: topData } = useFetchOverviewTital();
|
||||
const { data: topData } = useFetchOverviewTotal();
|
||||
const {
|
||||
pagination: { total: fileTotal },
|
||||
} = useFetchDocumentList(false);
|
||||
|
||||
Reference in New Issue
Block a user