Files
dify/api/tests/unit_tests/services/knowledge/external/test_application.py
T

82 lines
3.3 KiB
Python

from collections.abc import Callable
from unittest.mock import MagicMock, create_autospec
import pytest
from machinery.context import RequestContext
from services.knowledge.dataset_access import AccessibleDataset, DatasetAccess, DatasetAccessDeniedError
from services.knowledge.external.application import (
ExternalHitTestingError,
ExternalKnowledgeApplicationService,
ExternalKnowledgeOperations,
)
from services.knowledge.resource_scope import DatasetRef
CONTEXT = RequestContext("request", None, "actor", "tenant")
@pytest.fixture
def operations() -> MagicMock:
return create_autospec(ExternalKnowledgeOperations, instance=True, spec_set=True)
@pytest.fixture
def access() -> MagicMock:
result = create_autospec(DatasetAccess, instance=True, spec_set=True)
result.require_accessible.return_value = AccessibleDataset("dataset", "tenant")
return result
@pytest.fixture
def service(operations: MagicMock, access: MagicMock) -> ExternalKnowledgeApplicationService:
return ExternalKnowledgeApplicationService(dataset_access=access, operations=operations)
@pytest.mark.parametrize("settings", [{}, {"endpoint": "https://example"}, {"api_key": "secret"}])
@pytest.mark.parametrize(
"method", [ExternalKnowledgeApplicationService.create_template, ExternalKnowledgeApplicationService.update_template]
)
def test_template_validation_prevents_io(
service: ExternalKnowledgeApplicationService,
operations: MagicMock,
settings: dict[str, str],
method: Callable[..., object],
) -> None:
extra: dict[str, str] = (
{"template_id": "template"} if method is ExternalKnowledgeApplicationService.update_template else {}
)
with pytest.raises(ValueError, match="(?:api list is empty|api_key is required|endpoint is required)"):
method(service, CONTEXT, name="API", settings=settings, **extra)
assert operations.mock_calls == []
def test_retrieval_authorization_precedes_validation_and_network(
service: ExternalKnowledgeApplicationService, access: MagicMock, operations: MagicMock
) -> None:
access.require_accessible.side_effect = DatasetAccessDeniedError()
with pytest.raises(DatasetAccessDeniedError):
service.hit_testing(CONTEXT, dataset_id="dataset", query="", retrieval_model=None, metadata_filters=None)
assert operations.mock_calls == []
@pytest.mark.parametrize("query", ["", "a" * 251])
def test_invalid_query_prevents_retrieval(
service: ExternalKnowledgeApplicationService, operations: MagicMock, query: str
) -> None:
with pytest.raises(ValueError, match="(?:Query cannot exceed 250 characters|Query or attachment_ids is required)"):
service.hit_testing(CONTEXT, dataset_id="dataset", query=query, retrieval_model=None, metadata_filters=None)
operations.retrieve.assert_not_called()
def test_retrieval_failure_is_neutral_and_receives_owned_ref(
service: ExternalKnowledgeApplicationService, operations: MagicMock
) -> None:
operations.retrieve.side_effect = RuntimeError("unavailable")
with pytest.raises(ExternalHitTestingError, match="unavailable"):
service.hit_testing(
CONTEXT, dataset_id="dataset", query="query", retrieval_model={"top_k": 2}, metadata_filters=None
)
operations.retrieve.assert_called_once_with(
CONTEXT, DatasetRef("tenant", "dataset"), query="query", retrieval_model={"top_k": 2}, metadata_filters=None
)