mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 02:28:30 +08:00
test: move RAG pipeline controller coverage to unit tests (#38939)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
22429d275a
commit
70462d96bd
@ -1,259 +1,42 @@
|
||||
"""Testcontainers integration tests for rag_pipeline controller endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
"""Integration coverage for customized pipeline template persistence."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from inspect import unwrap
|
||||
from typing import cast
|
||||
from unittest.mock import ANY, MagicMock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from sqlalchemy.orm import Session
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from controllers.common.schema import JsonResponseWithStatus
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline import (
|
||||
CustomizedPipelineTemplateApi,
|
||||
PipelineTemplateDetailApi,
|
||||
PipelineTemplateListApi,
|
||||
PublishCustomizedPipelineTemplateApi,
|
||||
)
|
||||
from models.account import Account
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline import CustomizedPipelineTemplateApi
|
||||
from models.dataset import PipelineCustomizedTemplate
|
||||
|
||||
|
||||
class TestPipelineTemplateListApi:
|
||||
@pytest.fixture
|
||||
def app(self, flask_app_with_containers: Flask) -> Flask:
|
||||
return flask_app_with_containers
|
||||
def test_export_customized_pipeline_template_from_database(
|
||||
flask_app_with_containers: Flask,
|
||||
db_session_with_containers: Session,
|
||||
) -> None:
|
||||
api = CustomizedPipelineTemplateApi()
|
||||
method = unwrap(cast(Callable[..., JsonResponseWithStatus], api.post))
|
||||
template = PipelineCustomizedTemplate(
|
||||
tenant_id=str(uuid4()),
|
||||
name="Test Template",
|
||||
description="Test",
|
||||
chunk_structure="hierarchical",
|
||||
icon={"icon": "📘"},
|
||||
position=0,
|
||||
yaml_content="yaml-data",
|
||||
install_count=0,
|
||||
language="en-US",
|
||||
created_by=str(uuid4()),
|
||||
)
|
||||
db_session_with_containers.add(template)
|
||||
db_session_with_containers.commit()
|
||||
db_session_with_containers.expire_all()
|
||||
|
||||
def test_get_success(self, app: Flask) -> None:
|
||||
api = PipelineTemplateListApi()
|
||||
method = unwrap(api.get)
|
||||
with flask_app_with_containers.test_request_context("/"):
|
||||
response, status = method(api, template.id)
|
||||
|
||||
templates = {
|
||||
"pipeline_templates": [
|
||||
{
|
||||
"id": "t1",
|
||||
"name": "Template",
|
||||
"description": "Description",
|
||||
"icon": {"icon": "📘", "icon_type": "emoji", "icon_background": "#fff"},
|
||||
"position": 1,
|
||||
"chunk_structure": "general",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/?type=built-in&language=en-US"),
|
||||
patch(
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline.RagPipelineService.get_pipeline_templates",
|
||||
return_value=templates,
|
||||
),
|
||||
):
|
||||
response, status = method(api, MagicMock(), str(uuid4()))
|
||||
|
||||
assert status == 200
|
||||
assert response == {
|
||||
"pipeline_templates": [
|
||||
{
|
||||
**templates["pipeline_templates"][0],
|
||||
"copyright": None,
|
||||
"privacy_policy": None,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class TestPipelineTemplateDetailApi:
|
||||
@pytest.fixture
|
||||
def app(self, flask_app_with_containers: Flask) -> Flask:
|
||||
return flask_app_with_containers
|
||||
|
||||
def test_get_success(self, app: Flask) -> None:
|
||||
api = PipelineTemplateDetailApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
nodes: list[dict[str, object]] = []
|
||||
edges: list[dict[str, object]] = []
|
||||
viewport: dict[str, int] = {"x": 0, "y": 0, "zoom": 1}
|
||||
template = {
|
||||
"id": "tpl-1",
|
||||
"name": "Template",
|
||||
"icon_info": {"icon": "📘", "icon_type": "emoji", "icon_background": "#fff"},
|
||||
"description": "Description",
|
||||
"chunk_structure": "general",
|
||||
"export_data": "yaml-data",
|
||||
"graph": {"nodes": nodes, "edges": edges, "viewport": viewport},
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/?type=built-in"),
|
||||
patch(
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline.RagPipelineService.get_pipeline_template_detail",
|
||||
return_value=template,
|
||||
) as get_detail_mock,
|
||||
):
|
||||
response, status = method(api, MagicMock(), "tpl-1")
|
||||
|
||||
assert status == 200
|
||||
assert response == {**template, "created_by": None}
|
||||
get_detail_mock.assert_called_once_with("tpl-1", type="built-in", session=ANY)
|
||||
|
||||
def test_get_returns_404_when_template_not_found(self, app: Flask) -> None:
|
||||
api = PipelineTemplateDetailApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
with (
|
||||
app.test_request_context("/?type=built-in"),
|
||||
patch(
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline.RagPipelineService.get_pipeline_template_detail",
|
||||
return_value=None,
|
||||
) as get_detail_mock,
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), "non-existent-id")
|
||||
|
||||
get_detail_mock.assert_called_once_with("non-existent-id", type="built-in", session=ANY)
|
||||
|
||||
def test_get_returns_404_for_customized_type_not_found(self, app: Flask) -> None:
|
||||
api = PipelineTemplateDetailApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
with (
|
||||
app.test_request_context("/?type=customized"),
|
||||
patch(
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline.RagPipelineService.get_pipeline_template_detail",
|
||||
return_value=None,
|
||||
) as get_detail_mock,
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, MagicMock(), "non-existent-id")
|
||||
|
||||
get_detail_mock.assert_called_once_with("non-existent-id", type="customized", session=ANY)
|
||||
|
||||
|
||||
class TestCustomizedPipelineTemplateApi:
|
||||
@pytest.fixture
|
||||
def app(self, flask_app_with_containers: Flask) -> Flask:
|
||||
return flask_app_with_containers
|
||||
|
||||
def test_patch_success(self, app: Flask) -> None:
|
||||
api = CustomizedPipelineTemplateApi()
|
||||
method = unwrap(api.patch)
|
||||
account = Account(name="Test User", email="test@example.com")
|
||||
account.id = str(uuid4())
|
||||
tenant_id = str(uuid4())
|
||||
|
||||
payload = {
|
||||
"name": "Template",
|
||||
"description": "Desc",
|
||||
"icon_info": {"icon": "📘"},
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
patch(
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline.RagPipelineService.update_customized_pipeline_template"
|
||||
) as update_mock,
|
||||
):
|
||||
response, status = method(api, tenant_id, account, "tpl-1")
|
||||
|
||||
update_mock.assert_called_once()
|
||||
assert update_mock.call_args.args[2] is account
|
||||
assert update_mock.call_args.args[3] == tenant_id
|
||||
assert status == 204
|
||||
assert response == ""
|
||||
|
||||
def test_delete_success(self, app: Flask) -> None:
|
||||
api = CustomizedPipelineTemplateApi()
|
||||
method = unwrap(api.delete)
|
||||
tenant_id = str(uuid4())
|
||||
|
||||
with (
|
||||
app.test_request_context("/"),
|
||||
patch(
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline.RagPipelineService.delete_customized_pipeline_template"
|
||||
) as delete_mock,
|
||||
):
|
||||
response, status = method(api, tenant_id, "tpl-1")
|
||||
|
||||
delete_mock.assert_called_once_with("tpl-1", tenant_id, session=ANY)
|
||||
assert status == 204
|
||||
assert response == ""
|
||||
|
||||
def test_post_success(self, app: Flask, db_session_with_containers: Session) -> None:
|
||||
api = CustomizedPipelineTemplateApi()
|
||||
method = unwrap(cast(Callable[..., JsonResponseWithStatus], api.post))
|
||||
|
||||
tenant_id = str(uuid4())
|
||||
template = PipelineCustomizedTemplate(
|
||||
tenant_id=tenant_id,
|
||||
name="Test Template",
|
||||
description="Test",
|
||||
chunk_structure="hierarchical",
|
||||
icon={"icon": "📘"},
|
||||
position=0,
|
||||
yaml_content="yaml-data",
|
||||
install_count=0,
|
||||
language="en-US",
|
||||
created_by=str(uuid4()),
|
||||
)
|
||||
db_session_with_containers.add(template)
|
||||
db_session_with_containers.commit()
|
||||
db_session_with_containers.expire_all()
|
||||
|
||||
with app.test_request_context("/"):
|
||||
response, status = method(api, template.id)
|
||||
|
||||
assert status == 200
|
||||
assert response == {"data": "yaml-data"}
|
||||
|
||||
def test_post_template_not_found(self, app: Flask) -> None:
|
||||
api = CustomizedPipelineTemplateApi()
|
||||
method = unwrap(cast(Callable[..., JsonResponseWithStatus], api.post))
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, str(uuid4()))
|
||||
|
||||
|
||||
class TestPublishCustomizedPipelineTemplateApi:
|
||||
@pytest.fixture
|
||||
def app(self, flask_app_with_containers: Flask) -> Flask:
|
||||
return flask_app_with_containers
|
||||
|
||||
def test_post_success(self, app: Flask) -> None:
|
||||
api = PublishCustomizedPipelineTemplateApi()
|
||||
method = unwrap(api.post)
|
||||
account = Account(name="Test User", email="test@example.com")
|
||||
account.id = str(uuid4())
|
||||
tenant_id = str(uuid4())
|
||||
|
||||
payload = {
|
||||
"name": "Template",
|
||||
"description": "Desc",
|
||||
"icon_info": {"icon": "📘"},
|
||||
}
|
||||
|
||||
service = MagicMock()
|
||||
|
||||
with (
|
||||
app.test_request_context("/", json=payload),
|
||||
patch.object(type(console_ns), "payload", payload),
|
||||
patch(
|
||||
"controllers.console.datasets.rag_pipeline.rag_pipeline.RagPipelineService",
|
||||
return_value=service,
|
||||
),
|
||||
):
|
||||
response, status = method(api, tenant_id, account, "pipeline-1")
|
||||
|
||||
service.publish_customized_pipeline_template.assert_called_once()
|
||||
assert service.publish_customized_pipeline_template.call_args.args[2] is account
|
||||
assert service.publish_customized_pipeline_template.call_args.args[3] == tenant_id
|
||||
assert status == 204
|
||||
assert response == ""
|
||||
assert status == 200
|
||||
assert response == {"data": "yaml-data"}
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from inspect import unwrap
|
||||
from unittest.mock import Mock, PropertyMock, patch
|
||||
from unittest.mock import PropertyMock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from sqlalchemy import Engine
|
||||
from sqlalchemy.orm import Session
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from controllers.console import console_ns
|
||||
@ -17,6 +20,7 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline import (
|
||||
)
|
||||
from models.account import Account
|
||||
from models.dataset import PipelineCustomizedTemplate
|
||||
from models.engine import db
|
||||
from services.entities.knowledge_entities.rag_pipeline_entities import PipelineTemplateInfoEntity
|
||||
|
||||
|
||||
@ -57,8 +61,19 @@ def _account() -> Account:
|
||||
return account
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def database_app() -> Iterator[Flask]:
|
||||
app = Flask(__name__)
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
|
||||
db.init_app(app)
|
||||
|
||||
with app.app_context():
|
||||
PipelineCustomizedTemplate.__table__.create(db.engine)
|
||||
yield app
|
||||
|
||||
|
||||
class TestPipelineTemplateListApi:
|
||||
def test_get_uses_query_defaults_and_serializes_nullable_fields(self, app: Flask) -> None:
|
||||
def test_get_uses_query_defaults_and_serializes_nullable_fields(self, app: Flask, sqlite_engine: Engine) -> None:
|
||||
api = PipelineTemplateListApi()
|
||||
method = unwrap(api.get)
|
||||
tenant_id = "tenant-1"
|
||||
@ -70,10 +85,11 @@ class TestPipelineTemplateListApi:
|
||||
return {"pipeline_templates": [_template_item()]}
|
||||
|
||||
with (
|
||||
Session(sqlite_engine) as session,
|
||||
app.test_request_context("/rag/pipeline/templates"),
|
||||
patch.object(module.RagPipelineService, "get_pipeline_templates", side_effect=get_pipeline_templates),
|
||||
):
|
||||
response, status = method(api, Mock(), tenant_id)
|
||||
response, status = method(api, session, tenant_id)
|
||||
|
||||
assert status == 200
|
||||
assert service_calls == [("built-in", "en-US", tenant_id)]
|
||||
@ -87,7 +103,7 @@ class TestPipelineTemplateListApi:
|
||||
]
|
||||
}
|
||||
|
||||
def test_get_passes_explicit_query_to_service(self, app: Flask) -> None:
|
||||
def test_get_passes_explicit_query_to_service(self, app: Flask, sqlite_engine: Engine) -> None:
|
||||
api = PipelineTemplateListApi()
|
||||
method = unwrap(api.get)
|
||||
tenant_id = "tenant-1"
|
||||
@ -99,10 +115,11 @@ class TestPipelineTemplateListApi:
|
||||
return {"pipeline_templates": []}
|
||||
|
||||
with (
|
||||
Session(sqlite_engine) as session,
|
||||
app.test_request_context("/rag/pipeline/templates?type=customized&language=ja-JP"),
|
||||
patch.object(module.RagPipelineService, "get_pipeline_templates", side_effect=get_pipeline_templates),
|
||||
):
|
||||
response, status = method(api, Mock(), tenant_id)
|
||||
response, status = method(api, session, tenant_id)
|
||||
|
||||
assert status == 200
|
||||
assert response == {"pipeline_templates": []}
|
||||
@ -110,7 +127,7 @@ class TestPipelineTemplateListApi:
|
||||
|
||||
|
||||
class TestPipelineTemplateDetailApi:
|
||||
def test_get_serializes_template_detail(self, app: Flask) -> None:
|
||||
def test_get_serializes_template_detail(self, app: Flask, sqlite_engine: Engine) -> None:
|
||||
api = PipelineTemplateDetailApi()
|
||||
method = unwrap(api.get)
|
||||
service_calls: list[tuple[str, str]] = []
|
||||
@ -121,6 +138,7 @@ class TestPipelineTemplateDetailApi:
|
||||
return _template_detail()
|
||||
|
||||
with (
|
||||
Session(sqlite_engine) as session,
|
||||
app.test_request_context("/rag/pipeline/templates/template-1?type=customized"),
|
||||
patch.object(
|
||||
module.RagPipelineService,
|
||||
@ -128,13 +146,13 @@ class TestPipelineTemplateDetailApi:
|
||||
side_effect=get_pipeline_template_detail,
|
||||
),
|
||||
):
|
||||
response, status = method(api, Mock(), "template-1")
|
||||
response, status = method(api, session, "template-1")
|
||||
|
||||
assert status == 200
|
||||
assert response == {**_template_detail(), "created_by": None}
|
||||
assert service_calls == [("template-1", "customized")]
|
||||
|
||||
def test_get_raises_not_found_without_custom_response_body(self, app: Flask) -> None:
|
||||
def test_get_raises_not_found_without_custom_response_body(self, app: Flask, sqlite_engine: Engine) -> None:
|
||||
api = PipelineTemplateDetailApi()
|
||||
method = unwrap(api.get)
|
||||
|
||||
@ -142,15 +160,16 @@ class TestPipelineTemplateDetailApi:
|
||||
del template_id, type, session
|
||||
|
||||
with (
|
||||
Session(sqlite_engine) as session,
|
||||
app.test_request_context("/rag/pipeline/templates/missing"),
|
||||
patch.object(
|
||||
module.RagPipelineService,
|
||||
"get_pipeline_template_detail",
|
||||
side_effect=get_pipeline_template_detail,
|
||||
),
|
||||
pytest.raises(NotFound),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, Mock(), "missing")
|
||||
method(api, session, "missing")
|
||||
|
||||
|
||||
class TestCustomizedPipelineTemplateApi:
|
||||
@ -256,7 +275,10 @@ class TestCustomizedPipelineTemplateApi:
|
||||
assert (response, status) == ("", 204)
|
||||
assert deleted_templates == [("template-1", tenant_id)]
|
||||
|
||||
def test_post_exports_yaml_from_orm_template(self, app: Flask) -> None:
|
||||
def test_post_exports_yaml_from_orm_template(
|
||||
self,
|
||||
database_app: Flask,
|
||||
) -> None:
|
||||
api = CustomizedPipelineTemplateApi()
|
||||
method = unwrap(api.post)
|
||||
template = PipelineCustomizedTemplate(
|
||||
@ -271,64 +293,26 @@ class TestCustomizedPipelineTemplateApi:
|
||||
language="en-US",
|
||||
created_by="00000000-0000-0000-0000-000000000002",
|
||||
)
|
||||
db.session.add(template)
|
||||
db.session.commit()
|
||||
|
||||
class Session:
|
||||
def scalar(self, stmt: object) -> PipelineCustomizedTemplate:
|
||||
return template
|
||||
|
||||
class SessionContext:
|
||||
def __enter__(self) -> Session:
|
||||
return Session()
|
||||
|
||||
def __exit__(self, exc_type: object, exc: object, tb: object) -> bool:
|
||||
return False
|
||||
|
||||
class SessionMaker:
|
||||
def begin(self) -> SessionContext:
|
||||
return SessionContext()
|
||||
|
||||
class Database:
|
||||
engine = object()
|
||||
|
||||
with (
|
||||
app.test_request_context("/rag/pipeline/customized/templates/template-1", method="POST"),
|
||||
patch.object(module, "db", Database()),
|
||||
patch.object(module, "sessionmaker", return_value=SessionMaker()),
|
||||
):
|
||||
response, status = method(api, "template-1")
|
||||
with database_app.test_request_context("/rag/pipeline/customized/templates/template-1", method="POST"):
|
||||
response, status = method(api, template.id)
|
||||
|
||||
assert status == 200
|
||||
assert response == {"data": "dsl: value"}
|
||||
|
||||
def test_post_raises_when_template_is_missing(self, app: Flask) -> None:
|
||||
def test_post_raises_when_template_is_missing(
|
||||
self,
|
||||
database_app: Flask,
|
||||
) -> None:
|
||||
api = CustomizedPipelineTemplateApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
class Session:
|
||||
def scalar(self, stmt: object) -> None:
|
||||
return None
|
||||
|
||||
class SessionContext:
|
||||
def __enter__(self) -> Session:
|
||||
return Session()
|
||||
|
||||
def __exit__(self, exc_type: object, exc: object, tb: object) -> bool:
|
||||
return False
|
||||
|
||||
class SessionMaker:
|
||||
def begin(self) -> SessionContext:
|
||||
return SessionContext()
|
||||
|
||||
class Database:
|
||||
engine = object()
|
||||
|
||||
with (
|
||||
app.test_request_context("/rag/pipeline/customized/templates/missing", method="POST"),
|
||||
patch.object(module, "db", Database()),
|
||||
patch.object(module, "sessionmaker", return_value=SessionMaker()),
|
||||
database_app.test_request_context("/rag/pipeline/customized/templates/missing", method="POST"),
|
||||
pytest.raises(ValueError, match="Customized pipeline template not found"),
|
||||
):
|
||||
with pytest.raises(ValueError, match="Customized pipeline template not found"):
|
||||
method(api, "missing")
|
||||
method(api, "44444444-4444-4444-4444-444444444444")
|
||||
|
||||
|
||||
class TestPublishCustomizedPipelineTemplateApi:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user