From 70462d96bd4015d782373fd8ede5298e0214dddd Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 15 Jul 2026 12:59:57 +0900 Subject: [PATCH] test: move RAG pipeline controller coverage to unit tests (#38939) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../rag_pipeline/test_rag_pipeline.py | 271 ++---------------- .../rag_pipeline/test_rag_pipeline.py | 104 +++---- 2 files changed, 71 insertions(+), 304 deletions(-) diff --git a/api/tests/test_containers_integration_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py b/api/tests/test_containers_integration_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py index 6c73b0010ed..e6af51d6022 100644 --- a/api/tests/test_containers_integration_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py +++ b/api/tests/test_containers_integration_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py @@ -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"} diff --git a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py index 39a6fa65a06..c39ed13a63c 100644 --- a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py +++ b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py @@ -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: