deer-flow/backend/tests/test_knowledge_scope.py
zhangwei-way b6503e9a35
feat(knowledge): add per-message RAGFlow retrieval scope (#5238)
* feat(knowledge): integrate RAGFlow retrieval and management

* test(knowledge): cover merged listing tool

* feat(knowledge): add per-message retrieval scope

* chore(docs): remove unrelated document

* docs(knowledge): add interaction screenshots

* feat(knowledge): simplify scope selector trigger

* docs(knowledge): refresh selector screenshot

* feat(knowledge): defer standalone management

* docs(knowledge): show chat-only scope UI

* fix(knowledge): honor scope on clarification replies

* fix(knowledge): harden scoped replay validation

* docs(knowledge): clarify replay scope precedence

* fix(knowledge): keep provider settings on tools

* fix(config): preserve tools-only knowledge settings

* fix(knowledge): submit custom assistant identity

* refactor(knowledge): trim PR scope changes

* fix(knowledge): sanitize document scope display

* feat(knowledge): enable scope selection in main chat

* fix(knowledge): emphasize active scope icon without button frame

* fix(knowledge): close context scrubbing and refresh e2e checks

* fix(knowledge): preserve idempotent canonical retries

* fix(knowledge): accept promptless conversation runs

* style(knowledge): format backend regression tests

* chore(knowledge): trim PR scope and fix frontend format

* fix(knowledge): remove shared-scope notice

* fix(knowledge): remove scope persistence notice

* docs(knowledge): include main chat in catalog scope

* fix(knowledge): preserve scope recovery and upgrades

* fix(config): preserve LightRAG knowledge upgrades

---------

Co-authored-by: foreleven <for-eleven@hotmail.com>
2026-09-18 16:59:31 +08:00

240 lines
7.3 KiB
Python

import json
import pytest
from pydantic import ValidationError
from deerflow.knowledge_scope import (
KNOWLEDGE_SCOPE_KEY,
KnowledgeScope,
canonicalize_knowledge_scope,
execution_scope,
)
def test_all_and_disabled_canonicalize_without_selection_fields() -> None:
assert canonicalize_knowledge_scope({"version": 1, "mode": "all"}) == {
"version": 1,
"mode": "all",
}
assert canonicalize_knowledge_scope({"version": 1, "mode": "disabled"}) == {
"version": 1,
"mode": "disabled",
}
@pytest.mark.parametrize(
"payload",
[
{"version": 2, "mode": "all"},
{"version": 1, "mode": "all", "future": True},
{"version": 1, "mode": "all", "dataset_ids": ["dataset-a"]},
{"version": 1, "mode": "disabled", "display": {"datasets": []}},
{"version": 1, "mode": "selected", "dataset_ids": []},
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"document_filters": [{"dataset_id": "dataset-a", "document_ids": []}],
},
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"document_filters": [
{"dataset_id": "dataset-a", "document_ids": ["doc-a"]},
{"dataset_id": "dataset-a", "document_ids": ["doc-b"]},
],
},
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"document_filters": [{"dataset_id": "dataset-b", "document_ids": ["doc-a"]}],
},
],
)
def test_invalid_scope_shapes_are_rejected(payload: dict) -> None:
with pytest.raises(ValidationError):
canonicalize_knowledge_scope(payload)
def test_selected_scope_trims_and_stably_deduplicates_ids() -> None:
scope = canonicalize_knowledge_scope(
{
"version": 1,
"mode": "selected",
"dataset_ids": [" dataset-a ", "dataset-b", "dataset-a"],
"document_filters": [
{
"dataset_id": " dataset-b ",
"document_ids": [" doc-1 ", "doc-2", "doc-1"],
}
],
}
)
assert scope == {
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a", "dataset-b"],
"document_filters": [{"dataset_id": "dataset-b", "document_ids": ["doc-1", "doc-2"]}],
}
def test_execution_scope_drops_untrusted_display_snapshot() -> None:
scope = canonicalize_knowledge_scope(
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"display": {"datasets": [{"id": "dataset-a", "name": "Agriculture", "documents": []}]},
}
)
assert execution_scope(scope) == {
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
}
assert KNOWLEDGE_SCOPE_KEY == "knowledge_scope"
@pytest.mark.parametrize(
"payload",
[
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"display": {"datasets": [{"id": "dataset-b", "name": "Other"}]},
},
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"document_filters": [{"dataset_id": "dataset-a", "document_ids": ["doc-a"]}],
"display": {
"datasets": [
{
"id": "dataset-a",
"name": "Agriculture",
"documents": [{"id": "doc-b", "name": "Other.pdf"}],
}
]
},
},
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"display": {
"datasets": [
{"id": "dataset-a", "name": "First"},
{"id": "dataset-a", "name": "Duplicate"},
]
},
},
],
)
def test_display_must_be_unique_and_related_to_execution_selection(payload: dict) -> None:
with pytest.raises(ValidationError):
canonicalize_knowledge_scope(payload)
def test_scope_capacity_limits_are_rejected_without_truncation() -> None:
with pytest.raises(ValidationError):
canonicalize_knowledge_scope(
{
"version": 1,
"mode": "selected",
"dataset_ids": [f"dataset-{index}" for index in range(101)],
}
)
with pytest.raises(ValidationError):
canonicalize_knowledge_scope(
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"document_filters": [
{
"dataset_id": "dataset-a",
"document_ids": [f"doc-{index}" for index in range(1001)],
}
],
}
)
with pytest.raises(ValidationError):
canonicalize_knowledge_scope(
{
"version": 1,
"mode": "selected",
"dataset_ids": ["x" * 257],
}
)
def test_display_capacity_and_canonical_json_byte_limit_are_enforced() -> None:
with pytest.raises(ValidationError):
canonicalize_knowledge_scope(
{
"version": 1,
"mode": "selected",
"dataset_ids": [f"dataset-{index}" for index in range(21)],
"display": {"datasets": [{"id": f"dataset-{index}", "name": f"Dataset {index}"} for index in range(21)]},
}
)
oversized = {
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"document_filters": [
{
"dataset_id": "dataset-a",
"document_ids": [f"doc-{index}-{'x' * 240}" for index in range(300)],
}
],
}
assert len(json.dumps(oversized, ensure_ascii=False).encode()) > 64 * 1024
with pytest.raises(ValidationError):
canonicalize_knowledge_scope(oversized)
def test_model_rejects_more_than_fifty_display_documents_and_long_names() -> None:
document_ids = [f"doc-{index}" for index in range(51)]
with pytest.raises(ValidationError):
KnowledgeScope.model_validate(
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"document_filters": [{"dataset_id": "dataset-a", "document_ids": document_ids}],
"display": {
"datasets": [
{
"id": "dataset-a",
"name": "Agriculture",
"documents": [{"id": document_id, "name": f"Document {index}"} for index, document_id in enumerate(document_ids)],
}
]
},
}
)
with pytest.raises(ValidationError):
canonicalize_knowledge_scope(
{
"version": 1,
"mode": "selected",
"dataset_ids": ["dataset-a"],
"display": {
"datasets": [
{"id": "dataset-a", "name": "" * 257},
]
},
}
)