fix(ci): resolve langbot-plugin from PyPI and clear lint failures

CI on feat/sandbox failed across Unit Tests, Lint and Build Dev Image.
Root causes and fixes:

- pyproject.toml had a [tool.uv.sources] editable override pinning
  langbot-plugin to ../langbot-plugin-sdk. That path only exists in a
  paired local checkout, so `uv sync` failed on every CI runner
  ("Distribution not found"). Remove the override and regenerate uv.lock
  so langbot-plugin==0.4.0b1 resolves from PyPI, matching master.

- tests/integration/api/test_pipelines.py: the pipeline extensions
  endpoint now calls ap.skill_service.list_skills(); add the missing
  skill_service mock to the fake_pipeline_app fixture (the test came
  from master, the endpoint change from feat/sandbox).

- Apply ruff format to three src files and prettier to three web files
  that had committed formatting drift, failing `ruff format --check`
  and `pnpm lint`.
This commit is contained in:
Junyan Qin
2026-05-21 13:38:27 +08:00
parent a39c4d5665
commit 7cf4e58ed8
9 changed files with 3614 additions and 3631 deletions

View File

@@ -130,9 +130,6 @@ dev = [
"ruff>=0.11.9",
]
[tool.uv.sources]
langbot-plugin = { path = "../langbot-plugin-sdk", editable = true }
[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [

View File

@@ -24,7 +24,7 @@ class KnowledgeBaseRouterGroup(group.RouterGroup):
@self.route(
'/<knowledge_base_uuid>',
methods=['GET', 'DELETE', 'PUT'],
auth_type=group.AuthType. USER_TOKEN_OR_API_KEY,
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
)
async def handle_specific_knowledge_base(knowledge_base_uuid: str) -> quart.Response:
if quart.request.method == 'GET':

View File

@@ -32,6 +32,7 @@ class MCPRouterGroup(group.RouterGroup):
async def _(server_name: str) -> str:
"""获取、更新或删除MCP服务器配置"""
from urllib.parse import unquote
server_name = unquote(server_name)
server_data = await self.ap.mcp_service.get_mcp_server_by_name(server_name)
@@ -60,6 +61,7 @@ class MCPRouterGroup(group.RouterGroup):
async def _(server_name: str) -> str:
"""测试MCP服务器连接"""
from urllib.parse import unquote
server_name = unquote(server_name)
server_data = await quart.request.json
task_id = await self.ap.mcp_service.test_mcp_server(server_name=server_name, server_data=server_data)

View File

@@ -13,6 +13,7 @@ if typing.TYPE_CHECKING:
# This file is kept for potential future extensions but the text marker
# detection mechanism has been removed.
def register_activated_skill(
ap: app.Application,
query: pipeline_query.Query,

View File

@@ -20,6 +20,7 @@ pytestmark = pytest.mark.integration
# ============== FIXTURE FOR SYS.MODULES ISOLATION ==============
@pytest.fixture(scope='module')
def mock_circular_import_chain():
"""Break circular import chain for API controller."""
@@ -53,21 +54,25 @@ def mock_circular_import_chain():
):
# Import groups after mocking to populate preregistered_groups
import langbot.pkg.api.http.controller.groups.pipelines.pipelines as _pipelines # noqa: E402, F401
yield
# ============== FAKE APPLICATION WITH PIPELINE SERVICES ==============
@pytest.fixture(scope='module')
def fake_pipeline_app():
"""Create FakeApp with pipeline-specific services (module scope for reuse)."""
app = FakeApp()
# Pipeline config
app.instance_config.data.update({
app.instance_config.data.update(
{
'api': {'port': 5300},
'system': {'allow_modify_login_info': True, 'limitation': {}},
})
}
)
# Auth services
app.user_service = Mock()
@@ -79,11 +84,14 @@ def fake_pipeline_app():
# Pipeline service
app.pipeline_service = Mock()
app.pipeline_service.get_pipeline_metadata = AsyncMock(return_value=[
app.pipeline_service.get_pipeline_metadata = AsyncMock(
return_value=[
{'name': 'trigger', 'stages': []},
{'name': 'ai', 'stages': []},
])
app.pipeline_service.get_pipelines = AsyncMock(return_value=[
]
)
app.pipeline_service.get_pipelines = AsyncMock(
return_value=[
{
'uuid': 'test-pipeline-uuid',
'name': 'Test Pipeline',
@@ -92,12 +100,15 @@ def fake_pipeline_app():
'updated_at': '2024-01-01T00:00:00',
'is_default': False,
}
])
app.pipeline_service.get_pipeline = AsyncMock(return_value={
]
)
app.pipeline_service.get_pipeline = AsyncMock(
return_value={
'uuid': 'test-pipeline-uuid',
'name': 'Test Pipeline',
'config': {},
})
}
)
app.pipeline_service.create_pipeline = AsyncMock(return_value={'uuid': 'new-pipeline-uuid'})
app.pipeline_service.update_pipeline = AsyncMock(return_value={})
app.pipeline_service.delete_pipeline = AsyncMock()
@@ -112,6 +123,10 @@ def fake_pipeline_app():
app.mcp_service = Mock()
app.mcp_service.get_mcp_servers = AsyncMock(return_value=[])
# Skill service (for extensions endpoint)
app.skill_service = Mock()
app.skill_service.list_skills = AsyncMock(return_value=[])
# Plugin connector (for extensions endpoint)
app.plugin_connector.list_plugins = AsyncMock(return_value=[])
@@ -130,6 +145,7 @@ async def quart_test_client(fake_pipeline_app, http_controller_cls):
# ============== PIPELINE ENDPOINT TESTS ==============
@pytest.mark.usefixtures('mock_circular_import_chain')
class TestPipelineMetadataEndpoint:
"""Tests for /api/v1/pipelines/_/metadata endpoint."""
@@ -138,8 +154,7 @@ class TestPipelineMetadataEndpoint:
async def test_get_pipeline_metadata_success(self, quart_test_client):
"""GET /api/v1/pipelines/_/metadata returns metadata list."""
response = await quart_test_client.get(
'/api/v1/pipelines/_/metadata',
headers={'Authorization': 'Bearer test_token'}
'/api/v1/pipelines/_/metadata', headers={'Authorization': 'Bearer test_token'}
)
assert response.status_code == 200
@@ -162,10 +177,7 @@ class TestPipelinesListEndpoint:
@pytest.mark.asyncio
async def test_get_pipelines_success(self, quart_test_client):
"""GET /api/v1/pipelines returns pipeline list."""
response = await quart_test_client.get(
'/api/v1/pipelines',
headers={'Authorization': 'Bearer test_token'}
)
response = await quart_test_client.get('/api/v1/pipelines', headers={'Authorization': 'Bearer test_token'})
assert response.status_code == 200
data = await response.get_json()
@@ -176,8 +188,7 @@ class TestPipelinesListEndpoint:
async def test_get_pipelines_with_sort_param(self, quart_test_client):
"""GET pipelines with sort parameter."""
response = await quart_test_client.get(
'/api/v1/pipelines?sort_by=created_at&sort_order=DESC',
headers={'Authorization': 'Bearer test_token'}
'/api/v1/pipelines?sort_by=created_at&sort_order=DESC', headers={'Authorization': 'Bearer test_token'}
)
assert response.status_code == 200
@@ -193,8 +204,7 @@ class TestPipelinesCRUDEndpoints:
async def test_get_single_pipeline_success(self, quart_test_client):
"""GET /api/v1/pipelines/{uuid} returns pipeline."""
response = await quart_test_client.get(
'/api/v1/pipelines/test-pipeline-uuid',
headers={'Authorization': 'Bearer test_token'}
'/api/v1/pipelines/test-pipeline-uuid', headers={'Authorization': 'Bearer test_token'}
)
assert response.status_code == 200
@@ -208,7 +218,7 @@ class TestPipelinesCRUDEndpoints:
response = await quart_test_client.post(
'/api/v1/pipelines',
headers={'Authorization': 'Bearer test_token'},
json={'name': 'New Pipeline', 'config': {}}
json={'name': 'New Pipeline', 'config': {}},
)
assert response.status_code == 200
@@ -222,7 +232,7 @@ class TestPipelinesCRUDEndpoints:
response = await quart_test_client.put(
'/api/v1/pipelines/test-pipeline-uuid',
headers={'Authorization': 'Bearer test_token'},
json={'name': 'Updated Pipeline'}
json={'name': 'Updated Pipeline'},
)
assert response.status_code == 200
@@ -233,8 +243,7 @@ class TestPipelinesCRUDEndpoints:
async def test_delete_pipeline_success(self, quart_test_client):
"""DELETE /api/v1/pipelines/{uuid} deletes pipeline."""
response = await quart_test_client.delete(
'/api/v1/pipelines/test-pipeline-uuid',
headers={'Authorization': 'Bearer test_token'}
'/api/v1/pipelines/test-pipeline-uuid', headers={'Authorization': 'Bearer test_token'}
)
assert response.status_code == 200
@@ -245,8 +254,7 @@ class TestPipelinesCRUDEndpoints:
async def test_copy_pipeline_success(self, quart_test_client):
"""POST /api/v1/pipelines/{uuid}/copy copies pipeline."""
response = await quart_test_client.post(
'/api/v1/pipelines/test-pipeline-uuid/copy',
headers={'Authorization': 'Bearer test_token'}
'/api/v1/pipelines/test-pipeline-uuid/copy', headers={'Authorization': 'Bearer test_token'}
)
assert response.status_code == 200
@@ -263,8 +271,7 @@ class TestPipelineExtensionsEndpoint:
async def test_get_extensions(self, quart_test_client):
"""GET /api/v1/pipelines/{uuid}/extensions."""
response = await quart_test_client.get(
'/api/v1/pipelines/test-pipeline-uuid/extensions',
headers={'Authorization': 'Bearer test_token'}
'/api/v1/pipelines/test-pipeline-uuid/extensions', headers={'Authorization': 'Bearer test_token'}
)
# Should return 200 if pipeline found

7136
uv.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -51,9 +51,7 @@ export default function PluginComponentList({
)}
{!useBadge && (
<div
className="flex flex-row items-center justify-start gap-[0.2rem]"
>
<div className="flex flex-row items-center justify-start gap-[0.2rem]">
{kindIconMap[kind]}
{responsive ? (
<span className="hidden md:inline">