#!/usr/bin/env python3 """Regression tests for the Chat Skill's bundled Python scripts.""" import contextlib import importlib.util import io import re import types import unittest from pathlib import Path from unittest import mock ROOT = Path(__file__).resolve().parents[2] MULTI_ROOT = ROOT / 'skills' / 'multi' / 'dingtalk-chat' MONO_ROOT = ROOT / 'skills' / 'mono' SCRIPT_NAMES = ( 'chat_export_messages.py', 'chat_history_with_user.py', 'bot_broadcast.py', ) def load_script(name: str): path = MULTI_ROOT / 'scripts' / f'{name}.py' spec = importlib.util.spec_from_file_location(f'chat_skill_{name}', path) if spec is None or spec.loader is None: raise RuntimeError(f'cannot load {path}') module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module class ChatSkillScriptPathTest(unittest.TestCase): def test_shortcuts_use_progressive_discovery_without_static_catalog(self): text = (MULTI_ROOT / 'SKILL.md').read_text(encoding='utf-8') block = text.split('', 1)[1].split( '', 1 )[0] self.assertNotRegex(block, r'\|\s*`dws chat \+') for required in ( '精确脚本 / recipe > 匹配的公开 shortcut > reference 中的原子命令', 'dws shortcut list --service chat --compact --format json', '不接受 `--compact` 时去掉该 flag 重试', '不得猜测 `+` 命令名', '若该 leaf 暂未进入 Schema', 'dws --help', 'confirmation=user_required', ): self.assertIn(required, block) def test_chat_references_track_remote_shortcut_contract(self): chat_ref = (MULTI_ROOT / 'references' / 'chat.md').read_text( encoding='utf-8' ) message_ref = ( MULTI_ROOT / 'references' / 'chat' / 'chat-message.md' ).read_text(encoding='utf-8') conversation_ref = ( MULTI_ROOT / 'references' / 'chat' / 'chat-conversation.md' ).read_text(encoding='utf-8') for required in ( 'unknown flag: --compact', '若 leaf 暂未进入 Schema', '+messages-send --as ', '+messages-resource-download', ): self.assertIn(required, chat_ref) for required in ( '--receiver-open-dingtalk-id', '--content', '--flow-status', '--download-resources', '--overwrite', 'fileId:不需要消息上下文', ): self.assertIn(required, message_ref) for required in ( '+conversation-clear-messages', '+category-delete', '确认后才传 `--yes`', ): self.assertIn(required, conversation_ref) def test_markdown_python_links_resolve(self): link_re = re.compile(r'\[[^\]]+\.py\]\(([^)]+\.py)\)') docs = list(MULTI_ROOT.rglob('*.md')) docs.extend((MONO_ROOT / 'references').rglob('*.md')) missing = [] for doc in docs: text = doc.read_text(encoding='utf-8') for target in link_re.findall(text): resolved = (doc.parent / target).resolve() if not resolved.is_file(): missing.append(f'{doc.relative_to(ROOT)} -> {target}') self.assertEqual([], missing) def test_documented_invocations_use_python3_and_skill_relative_paths(self): docs = { MULTI_ROOT: ( MULTI_ROOT / 'SKILL.md', MULTI_ROOT / 'references' / '01-messaging.md', MULTI_ROOT / 'references' / 'chat.md', MULTI_ROOT / 'references' / 'chat' / 'chat-workflows.md', ), MONO_ROOT: ( MONO_ROOT / 'references' / 'best_practices' / '01-messaging.md', MONO_ROOT / 'references' / 'products' / 'chat.md', ), } bad_python = re.compile(r'(? {target} is missing' ) self.assertEqual([], errors) def test_mono_and_multi_script_copies_match(self): bad_python = re.compile(r'(?