Creator App Tests / PR Spam Gate (push) Skipped
Frontend Tests / PR Spam Gate (push) Skipped
NPM Format (website & console) / PR Spam Gate (push) Skipped
Pre-commit Checks / PR Spam Gate (push) Skipped
QwenPaw Creator Plugin Release / check-version (push) Canceled after 0s
QwenPaw Creator Plugin Release / upload-oss (push) Canceled after 0s
Tests / PR Spam Gate (push) Skipped
CodeQL / CodeQL (python) (push) Canceled after 0s
CodeQL / CodeQL (typescript) (push) Canceled after 0s
Creator App Tests / Creator Backend Tests (push) Canceled after 0s
Creator App Tests / Creator UI Build & Tests (push) Canceled after 0s
E2E Smoke Tests / Playwright UI Smoke (push) Canceled after 0s
Frontend Tests / Detect console changes (push) Canceled after 0s
NPM Format (website & console) / website format check (push) Canceled after 0s
NPM Format (website & console) / console format check (push) Canceled after 0s
Pre-commit Checks / Pre-commit (push) Canceled after 0s
QwenPaw Data Plugin Release / check-version (push) Canceled after 0s
Tests / Detect code changes (push) Canceled after 0s
Frontend Tests / Vitest Unit Tests (push) Canceled after 0s
Frontend Tests / Frontend Summary (push) Canceled after 0s
QwenPaw Data Plugin Release / upload-oss (push) Canceled after 0s
Tests / Maintainer Approval (push) Canceled after 0s
Tests / Unit Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - fallback (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p2 (push) Canceled after 0s
Tests / Coverage Report (push) Canceled after 0s
Tests / Test Summary (push) Canceled after 0s
145 lines
3.9 KiB
Python
145 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Tests for PromptBuilder and plugin prompt section registration."""
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from qwenpaw.agents.prompt_builder import PromptBuilder
|
|
from qwenpaw.plugins.registry import PluginRegistry
|
|
|
|
|
|
class _FakeAgent:
|
|
"""Minimal stand-in providing only what providers may receive."""
|
|
|
|
def __init__(self, *, agent_id: str = "datapaw") -> None:
|
|
self._request_context = {"agent_id": agent_id}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clean_prompt_sections():
|
|
"""Keep singleton registry state isolated for these tests."""
|
|
registry = PluginRegistry()
|
|
for plugin_id in ("test-a", "test-b", "broken-plugin"):
|
|
registry.unregister_plugin(plugin_id)
|
|
yield
|
|
for plugin_id in ("test-a", "test-b", "broken-plugin"):
|
|
registry.unregister_plugin(plugin_id)
|
|
|
|
|
|
def test_builder_orders_sections_after_host_anchor():
|
|
"""Plugin sections follow their declared host anchor."""
|
|
registry = PluginRegistry()
|
|
registry.register_prompt_section(
|
|
plugin_id="test-a",
|
|
name="plugin.master",
|
|
after="workspace",
|
|
agent_id="datapaw",
|
|
provider=lambda agent: "MASTER",
|
|
)
|
|
registry.register_prompt_section(
|
|
plugin_id="test-a",
|
|
name="plugin.env",
|
|
after="workspace",
|
|
agent_id="datapaw",
|
|
provider=lambda agent: "ENV",
|
|
)
|
|
|
|
builder = PromptBuilder(registry)
|
|
result = builder.build(
|
|
agent=_FakeAgent(),
|
|
agent_id="datapaw",
|
|
workspace="WORKSPACE",
|
|
multimodal="MULTIMODAL",
|
|
env_context="HOST_ENV",
|
|
)
|
|
|
|
assert result == (
|
|
"WORKSPACE\n\n" "MASTER\n\n" "ENV\n\n" "MULTIMODAL\n\n" "HOST_ENV"
|
|
)
|
|
|
|
|
|
def test_registry_rejects_invalid_anchor():
|
|
"""after must reference a valid host anchor."""
|
|
registry = PluginRegistry()
|
|
with pytest.raises(ValueError, match="must reference a host anchor"):
|
|
registry.register_prompt_section(
|
|
plugin_id="test-a",
|
|
name="plugin.bad",
|
|
after="nonexistent",
|
|
agent_id="datapaw",
|
|
provider=lambda agent: "X",
|
|
)
|
|
|
|
|
|
def test_builder_filters_by_agent_id():
|
|
"""agent_id limits which sections appear in the prompt."""
|
|
registry = PluginRegistry()
|
|
registry.register_prompt_section(
|
|
plugin_id="test-a",
|
|
name="plugin.datapaw",
|
|
after="workspace",
|
|
agent_id="datapaw",
|
|
provider=lambda agent: "DATAPAW",
|
|
)
|
|
registry.register_prompt_section(
|
|
plugin_id="test-b",
|
|
name="plugin.all",
|
|
after="workspace",
|
|
agent_id=None,
|
|
provider=lambda agent: "ALL",
|
|
)
|
|
|
|
builder = PromptBuilder(registry)
|
|
|
|
other = builder.build(
|
|
agent=_FakeAgent(agent_id="other"),
|
|
agent_id="other",
|
|
workspace="WORKSPACE",
|
|
)
|
|
assert other == "WORKSPACE\n\nALL"
|
|
|
|
registry.unregister_plugin("test-b")
|
|
datapaw = builder.build(
|
|
agent=_FakeAgent(),
|
|
agent_id="datapaw",
|
|
workspace="WORKSPACE",
|
|
)
|
|
assert datapaw == "WORKSPACE\n\nDATAPAW"
|
|
|
|
|
|
def test_builder_skips_empty_and_failed_providers():
|
|
"""Bad or empty providers do not break prompt assembly."""
|
|
registry = PluginRegistry()
|
|
registry.register_prompt_section(
|
|
plugin_id="test-a",
|
|
name="plugin.empty",
|
|
after="workspace",
|
|
agent_id="datapaw",
|
|
provider=lambda agent: "",
|
|
)
|
|
|
|
def _raise(_agent):
|
|
raise RuntimeError("boom")
|
|
|
|
registry.register_prompt_section(
|
|
plugin_id="broken-plugin",
|
|
name="plugin.broken",
|
|
after="workspace",
|
|
agent_id="datapaw",
|
|
provider=_raise,
|
|
)
|
|
|
|
with patch(
|
|
"qwenpaw.agents.prompt_builder.logger.exception",
|
|
) as log_exc:
|
|
result = PromptBuilder(registry).build(
|
|
agent=_FakeAgent(),
|
|
agent_id="datapaw",
|
|
workspace="WORKSPACE",
|
|
)
|
|
|
|
assert result == "WORKSPACE"
|
|
log_exc.assert_called_once()
|