← Files TopviewARCHIVED FILE

skills/operate-topview-canvas/scripts/test_timeline_contract.py

3.1 KB · Oct 2, 2026 · 00:09 UTC

↓ Download file

import unittest
from pathlib import Path


PLUGIN_ROOT = Path(__file__).resolve().parents[3]
API_PATH = PLUGIN_ROOT / "skills" / "operate-topview-canvas" / "references" / "canvas-mcp-api.md"
RECIPE_PATH = PLUGIN_ROOT / "skills" / "canvas-agent-workflows" / "references" / "recipes" / "timeline.md"
SKILL_PATH = PLUGIN_ROOT / "skills" / "operate-topview-canvas" / "SKILL.md"
WORKFLOW_ROOT = PLUGIN_ROOT / "skills" / "canvas-agent-workflows" / "references" / "workflows"


class TimelineContractTest(unittest.TestCase):
    def test_append_only_safety_gate_is_documented(self) -> None:
        api = API_PATH.read_text(encoding="utf-8")
        recipe = RECIPE_PATH.read_text(encoding="utf-8")
        skill = SKILL_PATH.read_text(encoding="utf-8")

        for text in (api, recipe, skill):
            self.assertIn("append_clip", text)
            self.assertNotIn("update_topview_canvas_timeline", text)
        self.assertIn("空稿", api)
        self.assertIn("empty", recipe)
        self.assertIn("exactly matches", skill)

    def test_operation_edit_and_metadata_retry_are_documented(self) -> None:
        api = API_PATH.read_text(encoding="utf-8")
        recipe = RECIPE_PATH.read_text(encoding="utf-8")
        skill = SKILL_PATH.read_text(encoding="utf-8")

        for text in (api, recipe, skill):
            self.assertIn("edit_topview_canvas_timeline", text)
            self.assertIn("MEDIA_INFO_PENDING", text)
            self.assertIn("commandId", text)
        self.assertIn("`operationResults[]`", api)
        self.assertIn("`createdClipIds[]`", api)
        self.assertNotIn("`split_clip`", api)
        self.assertNotIn("`detach_clip_audio`", api)
        self.assertNotIn("`set_aspect_ratio`", api)
        self.assertIn("`hiddenVisualTrackIds[]`", api)
        self.assertIn("`all_segments` 携带该参数会返回 400", api)
        self.assertIn("完全相同", api)
        self.assertIn("identical", skill)

    def test_timeline_recipe_is_linked_from_video_workflows(self) -> None:
        for name in ("short-film.md", "ecommerce-product-video.md", "social-media-video.md"):
            text = (WORKFLOW_ROOT / name).read_text(encoding="utf-8")
            self.assertIn("[recipes/timeline.md](../recipes/timeline.md)", text)

    def test_workspace_open_is_a_single_ui_action(self) -> None:
        skill = SKILL_PATH.read_text(encoding="utf-8")

        self.assertIn("Browser-native Canvas opening", skill)
        self.assertIn("use only `open_topview_canvas_workspace`", skill)
        self.assertIn("Do not call both", skill)

    def test_browser_plugin_uses_one_authorized_link_and_host_browser(self) -> None:
        skill = SKILL_PATH.read_text(encoding="utf-8")

        self.assertIn("Browser-native Canvas opening", skill)
        self.assertIn("call exactly one of `open_topview_canvas`", skill)
        self.assertIn("use an available host Browser/navigation capability", skill)
        self.assertIn("clickable Markdown fallback", skill)
        self.assertIn("never assemble or guess a Canvas URL", skill)
        self.assertIn("does not expose iframe-only helpers", skill)


if __name__ == "__main__":
    unittest.main()

SHA-256: 2b8a4398e9e57ec9561948738f5ac59b2b3310f20543c191b1ff23eb1b07c086