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:
Asuka Minato 2026-07-15 12:59:57 +09:00 committed by GitHub
parent 22429d275a
commit 70462d96bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 71 additions and 304 deletions

View File

@ -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"}

View File

@ -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: