from __future__ import annotations import importlib.util import json import subprocess import sys from pathlib import Path import pytest from deerflow.skills.parser import parse_skill_file from deerflow.skills.storage.local_skill_storage import LocalSkillStorage from deerflow.skills.types import SkillCategory from deerflow.skills.validation import _validate_skill_frontmatter BACKEND_ROOT = Path(__file__).resolve().parents[1] SKILL_ROOT = BACKEND_ROOT / "skills" / "public" / "cnn-page-generator" SCRIPT_PATH = SKILL_ROOT / "scripts" / "render_cnn_page.py" FAST_SCRIPT_PATH = SKILL_ROOT / "scripts" / "fast_page.py" LEAD_PROMPT_PATH = BACKEND_ROOT / "packages" / "harness" / "deerflow" / "agents" / "lead_agent" / "prompt.py" def _load_renderer(): spec = importlib.util.spec_from_file_location( "cnn_page_renderer", SCRIPT_PATH, ) assert spec and spec.loader module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def test_skill_is_discoverable_and_valid() -> None: valid, error, _metadata = _validate_skill_frontmatter(SKILL_ROOT) assert valid, error skill = parse_skill_file(SKILL_ROOT / "SKILL.md", SkillCategory.PUBLIC) assert skill is not None assert skill.name == "cnn-page-generator" assert "CNN" in skill.description discovered = LocalSkillStorage(host_path=str(SKILL_ROOT.parent.parent)).load_skills(enabled_only=True) discovered_by_name = {item.name: item for item in discovered} assert discovered_by_name["cnn-page-generator"].category == SkillCategory.PUBLIC assert discovered_by_name["cnn-page-generator"].enabled is True lead_prompt_source = LEAD_PROMPT_PATH.read_text(encoding="utf-8") assert "/mnt/skills/public/cnn-page-generator/SKILL.md" in lead_prompt_source assert "专用 CNN 路由" in lead_prompt_source def test_default_json_renders_editable_cnn_html(tmp_path: Path) -> None: renderer = _load_renderer() data = json.loads((SKILL_ROOT / "assets" / "default-data.json").read_text(encoding="utf-8")) summary = renderer.validate_document(data) template = (SKILL_ROOT / "assets" / "cnn-template.html").read_text(encoding="utf-8") runtime = (SKILL_ROOT / "assets" / "cnn-runtime.js").read_text(encoding="utf-8") html = renderer.render_document(data, template, runtime) assert summary["modules"] == list(renderer.MODULES) assert summary["counts"]["latest"] >= 1 assert renderer.DATA_MARKER not in html assert renderer.RUNTIME_MARKER not in html assert 'id="cnnPageData"' in html assert 'id="leadGrid"' in html assert 'id="videoGrid"' in html assert 'id="liveModal"' in html assert 'id="openSettings"' in html assert 'id="moduleTabs"' in html assert 'id="applyJson"' in html assert "uploadJsonMedia" in html assert "installInlineMediaReplace" in html assert "dfInlineMediaInput" in html assert 'input.accept = "image/*,video/*"' in html assert 'button.title = "上传图片或视频并替换"' in html assert "active.expected" not in html assert "previous.posterUrl" not in html assert "decorateBuiltInVisuals" in html assert "replaceBuiltInVisuals" not in html assert "readAsDataURL" in html assert '"brand": "CNN"' in html assert "CNN — 全球新闻现场" in html assert '"cnn-page-data-v1:" + pageFingerprint' in html assert "const readStorage = (key)" in html assert "window.localStorage.getItem" in html assert "const cachedData = localStorage.getItem" not in html assert 'CNN · 智能体新闻中心" in titled_html assert "CNN — 全球新闻现场" not in titled_html data["lead"]["primary"]["title"] = "" escaped_html = renderer.render_document(data, template, runtime) assert "