import json import re import unittest from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[3] BACKEND_ROOT = PROJECT_ROOT / 'shierfeng-fastapi-backend' FRONTEND_ROOT = PROJECT_ROOT / 'shierfeng-fastapi-frontend' AI_DEPENDENCIES = { 'agno', 'anthropic', 'cerebras-cloud-sdk', 'cohere', 'google-genai', 'groq', 'litellm', 'llama-api-client', 'mistralai', 'ollama', 'openai', 'portkey-ai', } AI_FRONTEND_DEPENDENCIES = { 'katex', 'markstream-vue', 'mermaid', 'shiki', 'stream-markdown', 'stream-monaco', 'uuid', 'vite-plugin-monaco-editor-esm', } def _parse_requirement_names(requirements_path: Path) -> set[str]: names = set() for raw_line in requirements_path.read_text(encoding='utf-8').splitlines(): line = raw_line.partition('#')[0].strip() if not line or line == '.': continue match = re.match(r'[A-Za-z0-9_.-]+', line) if match: names.add(match.group(0).lower().replace('_', '-')) return names class AiRemovalContractTest(unittest.TestCase): def test_ai_source_files_are_removed(self) -> None: remaining_files = [] module_ai = BACKEND_ROOT / 'module_ai' if module_ai.exists(): remaining_files.extend(module_ai.rglob('*.py')) explicit_files = ( BACKEND_ROOT / 'utils/ai_util.py', BACKEND_ROOT / 'utils/crypto_util.py', FRONTEND_ROOT / 'src/api/ai/chat.js', FRONTEND_ROOT / 'src/api/ai/model.js', FRONTEND_ROOT / 'src/views/ai/chat/components/AiMessage.vue', FRONTEND_ROOT / 'src/views/ai/chat/index.vue', FRONTEND_ROOT / 'src/views/ai/model/index.vue', FRONTEND_ROOT / 'src/assets/icons/svg/ai-chat.svg', FRONTEND_ROOT / 'src/assets/icons/svg/ai-manage.svg', FRONTEND_ROOT / 'src/assets/icons/svg/ai-model.svg', ) remaining_files.extend(path for path in explicit_files if path.exists()) self.assertEqual([], sorted(str(path.relative_to(PROJECT_ROOT)) for path in remaining_files)) def test_ai_dependencies_are_removed(self) -> None: backend_dependencies = set() for filename in ('requirements.txt',): backend_dependencies.update(_parse_requirement_names(BACKEND_ROOT / filename)) self.assertEqual([], sorted(AI_DEPENDENCIES & backend_dependencies)) package_json = json.loads((FRONTEND_ROOT / 'package.json').read_text(encoding='utf-8')) frontend_dependencies = set(package_json.get('dependencies', {})) frontend_dependencies.update(package_json.get('devDependencies', {})) self.assertEqual([], sorted(AI_FRONTEND_DEPENDENCIES & frontend_dependencies)) def test_initialization_sql_has_no_ai_or_external_menu(self) -> None: forbidden_terms = ('ai_models', 'ai_chat_config', 'ai_provider_type', '施尔丰仓库') violations = [] for filename in ('shierfeng-fastapi.sql',): sql = (BACKEND_ROOT / 'sql' / filename).read_text(encoding='utf-8').lower() violations.extend(f'{filename}: {term}' for term in forbidden_terms if term.lower() in sql) self.assertEqual([], violations) if __name__ == '__main__': unittest.main()