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
111 lines
3.7 KiB
Python
111 lines
3.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Tests for runtime prompt contributors."""
|
|
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from qwenpaw.runtime.prompt_contributors import (
|
|
WorkspacePromptFilesContributor,
|
|
build_default_prompt_manager,
|
|
)
|
|
|
|
|
|
def _ctx(tmp_path, system_prompt_files):
|
|
return SimpleNamespace(
|
|
workspace_dir=str(tmp_path),
|
|
agent_id="test_agent",
|
|
extras={
|
|
"agent_config": SimpleNamespace(
|
|
system_prompt_files=system_prompt_files,
|
|
language="en",
|
|
),
|
|
"heartbeat_enabled": False,
|
|
"language": "en",
|
|
"memory_manager": None,
|
|
},
|
|
)
|
|
|
|
|
|
def test_workspace_prompt_files_respects_disabled_files(tmp_path):
|
|
"""Configured prompt file list gates SOUL.md and PROFILE.md."""
|
|
(tmp_path / "AGENTS.md").write_text("agents body", encoding="utf-8")
|
|
(tmp_path / "SOUL.md").write_text("soul body", encoding="utf-8")
|
|
(tmp_path / "PROFILE.md").write_text("profile body", encoding="utf-8")
|
|
|
|
prompt = build_default_prompt_manager().build_sync(
|
|
_ctx(tmp_path, ["AGENTS.md"]),
|
|
)
|
|
|
|
assert "# AGENTS.md" in prompt
|
|
assert "agents body" in prompt
|
|
assert "# SOUL.md" not in prompt
|
|
assert "soul body" not in prompt
|
|
assert "# PROFILE.md" not in prompt
|
|
assert "profile body" not in prompt
|
|
|
|
|
|
def test_workspace_prompt_files_empty_list_disables_workspace_markdown(
|
|
tmp_path,
|
|
):
|
|
"""An empty configured list is meaningful and disables markdown files."""
|
|
(tmp_path / "AGENTS.md").write_text("agents body", encoding="utf-8")
|
|
(tmp_path / "SOUL.md").write_text("soul body", encoding="utf-8")
|
|
(tmp_path / "PROFILE.md").write_text("profile body", encoding="utf-8")
|
|
|
|
fragment = WorkspacePromptFilesContributor().contribute_sync(
|
|
_ctx(tmp_path, []),
|
|
)
|
|
|
|
assert fragment is None
|
|
|
|
|
|
def test_workspace_prompt_files_preserves_configured_order_and_custom_files(
|
|
tmp_path,
|
|
):
|
|
"""Configured file order is the rendered prompt order."""
|
|
(tmp_path / "AGENTS.md").write_text("agents body", encoding="utf-8")
|
|
(tmp_path / "PROFILE.md").write_text("profile body", encoding="utf-8")
|
|
(tmp_path / "CUSTOM.md").write_text("custom body", encoding="utf-8")
|
|
|
|
fragment = WorkspacePromptFilesContributor().contribute_sync(
|
|
_ctx(tmp_path, ["PROFILE.md", "CUSTOM.md", "AGENTS.md"]),
|
|
)
|
|
|
|
assert fragment is not None
|
|
assert fragment.index("# PROFILE.md") < fragment.index("# CUSTOM.md")
|
|
assert fragment.index("# CUSTOM.md") < fragment.index("# AGENTS.md")
|
|
|
|
|
|
def test_workspace_prompt_files_skips_parent_traversal(tmp_path):
|
|
"""Configured prompt files cannot escape the workspace via ``..``."""
|
|
outside = tmp_path.parent / f"{tmp_path.name}_secret.md"
|
|
outside.write_text("secret body", encoding="utf-8")
|
|
(tmp_path / "AGENTS.md").write_text("agents body", encoding="utf-8")
|
|
|
|
fragment = WorkspacePromptFilesContributor().contribute_sync(
|
|
_ctx(tmp_path, [f"../{outside.name}", "AGENTS.md"]),
|
|
)
|
|
|
|
assert fragment is not None
|
|
body = str(fragment)
|
|
assert "secret body" not in body
|
|
assert "agents body" in body
|
|
|
|
|
|
def test_workspace_prompt_files_skips_symlink_escape(tmp_path):
|
|
"""Configured prompt files cannot escape through symlinks."""
|
|
outside = tmp_path.parent / f"{tmp_path.name}_symlink_secret.md"
|
|
outside.write_text("secret body", encoding="utf-8")
|
|
link = tmp_path / "LINK.md"
|
|
try:
|
|
link.symlink_to(outside)
|
|
except (OSError, NotImplementedError):
|
|
pytest.skip("symlinks are not available on this platform")
|
|
|
|
fragment = WorkspacePromptFilesContributor().contribute_sync(
|
|
_ctx(tmp_path, ["LINK.md"]),
|
|
)
|
|
|
|
assert fragment is None
|