test_setup_scripts.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #!/usr/bin/env python3
  2. """
  3. Test setup scripts for correctness and path validation.
  4. Tests that bash scripts reference correct paths and are syntactically valid.
  5. """
  6. import subprocess
  7. import re
  8. from pathlib import Path
  9. import pytest
  10. class TestSetupMCPScript:
  11. """Test setup_mcp.sh for path correctness and syntax"""
  12. @pytest.fixture
  13. def script_path(self):
  14. """Get path to setup_mcp.sh"""
  15. return Path("setup_mcp.sh")
  16. @pytest.fixture
  17. def script_content(self, script_path):
  18. """Read setup_mcp.sh content"""
  19. with open(script_path, 'r') as f:
  20. return f.read()
  21. def test_setup_mcp_exists(self, script_path):
  22. """Test that setup_mcp.sh exists"""
  23. assert script_path.exists(), "setup_mcp.sh should exist"
  24. assert script_path.is_file(), "setup_mcp.sh should be a file"
  25. def test_bash_syntax_valid(self, script_path):
  26. """Test that setup_mcp.sh has valid bash syntax"""
  27. result = subprocess.run(
  28. ["bash", "-n", str(script_path)],
  29. capture_output=True,
  30. text=True
  31. )
  32. assert result.returncode == 0, f"Bash syntax error: {result.stderr}"
  33. def test_references_correct_mcp_directory(self, script_content):
  34. """Test that script references src/skill_seekers/mcp/ (v2.0.0 layout)"""
  35. # Should NOT reference old mcp/ or skill_seeker_mcp/ directories
  36. old_mcp_refs = re.findall(r'(?:^|[^a-z_])(?<!/)mcp/(?!\.json)', script_content, re.MULTILINE)
  37. old_skill_seeker_refs = re.findall(r'skill_seeker_mcp/', script_content)
  38. # Allow /mcp/ (as in src/skill_seekers/mcp/) but not standalone mcp/
  39. assert len(old_mcp_refs) == 0, f"Found {len(old_mcp_refs)} references to old 'mcp/' directory: {old_mcp_refs}"
  40. assert len(old_skill_seeker_refs) == 0, f"Found {len(old_skill_seeker_refs)} references to old 'skill_seeker_mcp/': {old_skill_seeker_refs}"
  41. # SHOULD reference src/skill_seekers/mcp/
  42. new_refs = re.findall(r'src/skill_seekers/mcp/', script_content)
  43. assert len(new_refs) >= 6, f"Expected at least 6 references to 'src/skill_seekers/mcp/', found {len(new_refs)}"
  44. def test_requirements_txt_path(self, script_content):
  45. """Test that script uses pip install -e . (v2.0.0 modern packaging)"""
  46. # v2.0.0 uses '-e .' (editable install) instead of requirements files
  47. # The actual command is "$PIP_INSTALL_CMD -e ."
  48. assert " -e ." in script_content or " -e." in script_content, \
  49. "Should use '-e .' for editable install (modern packaging)"
  50. # Should NOT reference old requirements.txt paths
  51. import re
  52. old_skill_seeker_refs = re.findall(r'skill_seeker_mcp/requirements\.txt', script_content)
  53. old_mcp_refs = re.findall(r'(?<!skill_seeker_)mcp/requirements\.txt', script_content)
  54. assert len(old_skill_seeker_refs) == 0, \
  55. f"Should NOT reference 'skill_seeker_mcp/requirements.txt' (found {len(old_skill_seeker_refs)})"
  56. assert len(old_mcp_refs) == 0, \
  57. f"Should NOT reference old 'mcp/requirements.txt' (found {len(old_mcp_refs)})"
  58. def test_server_py_path(self, script_content):
  59. """Test that server.py path is correct (v2.0.0 layout)"""
  60. import re
  61. assert "src/skill_seekers/mcp/server.py" in script_content, \
  62. "Should reference src/skill_seekers/mcp/server.py"
  63. # Should NOT reference old paths
  64. old_skill_seeker_refs = re.findall(r'skill_seeker_mcp/server\.py', script_content)
  65. old_mcp_refs = re.findall(r'(?<!/)(?<!skill_seekers/)mcp/server\.py', script_content)
  66. assert len(old_skill_seeker_refs) == 0, \
  67. f"Should NOT reference old 'skill_seeker_mcp/server.py' (found {len(old_skill_seeker_refs)})"
  68. assert len(old_mcp_refs) == 0, \
  69. f"Should NOT reference old 'mcp/server.py' (found {len(old_mcp_refs)})"
  70. def test_referenced_files_exist(self):
  71. """Test that all files referenced in setup_mcp.sh actually exist"""
  72. # Check critical paths (new src/ layout)
  73. assert Path("src/skill_seekers/mcp/server.py").exists(), \
  74. "src/skill_seekers/mcp/server.py should exist"
  75. assert Path("requirements.txt").exists(), \
  76. "requirements.txt should exist (root level)"
  77. def test_config_directory_exists(self):
  78. """Test that referenced config directory exists"""
  79. assert Path("configs/").exists(), "configs/ directory should exist"
  80. assert Path("configs/").is_dir(), "configs/ should be a directory"
  81. def test_script_is_executable(self, script_path):
  82. """Test that setup_mcp.sh is executable"""
  83. import os
  84. assert os.access(script_path, os.X_OK), "setup_mcp.sh should be executable"
  85. def test_json_config_path_format(self, script_content):
  86. """Test that JSON config examples use correct format (v2.0.0 layout)"""
  87. # Check for the config path format in the script
  88. assert '"$REPO_PATH/src/skill_seekers/mcp/server.py"' in script_content, \
  89. "Config should show correct server.py path with $REPO_PATH variable (v2.0.0 layout)"
  90. def test_no_hardcoded_paths(self, script_content):
  91. """Test that script doesn't contain hardcoded absolute paths"""
  92. # Check for suspicious absolute paths (but allow $REPO_PATH and ~/.config)
  93. hardcoded_paths = re.findall(r'(?<![$~])/mnt/[^\s"\']+', script_content)
  94. assert len(hardcoded_paths) == 0, f"Found hardcoded absolute paths: {hardcoded_paths}"
  95. def test_pytest_command_references(self, script_content):
  96. """Test that pytest commands reference correct test files"""
  97. # Check for test file references
  98. if "pytest" in script_content:
  99. assert "tests/test_mcp_server.py" in script_content, \
  100. "Should reference correct test file path"
  101. class TestBashScriptGeneral:
  102. """General tests for all bash scripts in repository"""
  103. @pytest.fixture
  104. def all_bash_scripts(self):
  105. """Find all bash scripts in repository root"""
  106. root = Path(".")
  107. return list(root.glob("*.sh"))
  108. def test_all_scripts_have_shebang(self, all_bash_scripts):
  109. """Test that all bash scripts have proper shebang"""
  110. for script in all_bash_scripts:
  111. with open(script, 'r') as f:
  112. first_line = f.readline()
  113. assert first_line.startswith("#!"), f"{script} should have shebang"
  114. assert "bash" in first_line.lower(), f"{script} should use bash"
  115. def test_all_scripts_syntax_valid(self, all_bash_scripts):
  116. """Test that all bash scripts have valid syntax"""
  117. for script in all_bash_scripts:
  118. result = subprocess.run(
  119. ["bash", "-n", str(script)],
  120. capture_output=True,
  121. text=True
  122. )
  123. assert result.returncode == 0, \
  124. f"{script} has syntax error: {result.stderr}"
  125. def test_all_scripts_use_set_e(self, all_bash_scripts):
  126. """Test that scripts use 'set -e' for error handling"""
  127. for script in all_bash_scripts:
  128. with open(script, 'r') as f:
  129. content = f.read()
  130. # Check for set -e or set -o errexit
  131. has_error_handling = (
  132. re.search(r'set\s+-[a-z]*e', content) or
  133. re.search(r'set\s+-o\s+errexit', content)
  134. )
  135. assert has_error_handling, \
  136. f"{script} should use 'set -e' for error handling"
  137. def test_no_deprecated_backticks(self, all_bash_scripts):
  138. """Test that scripts use $() instead of deprecated backticks"""
  139. for script in all_bash_scripts:
  140. with open(script, 'r') as f:
  141. content = f.read()
  142. # Allow backticks in comments
  143. lines = [line for line in content.split('\n') if not line.strip().startswith('#')]
  144. code_content = '\n'.join(lines)
  145. backticks = re.findall(r'`[^`]+`', code_content)
  146. assert len(backticks) == 0, \
  147. f"{script} uses deprecated backticks: {backticks}. Use $() instead"
  148. class TestMCPServerPaths:
  149. """Test that MCP server references are consistent across codebase"""
  150. def test_github_workflows_reference_correct_paths(self):
  151. """Test that GitHub workflows reference correct MCP paths"""
  152. workflow_file = Path(".github/workflows/tests.yml")
  153. if workflow_file.exists():
  154. with open(workflow_file, 'r') as f:
  155. content = f.read()
  156. # Should NOT reference old mcp/ directory
  157. assert "mcp/requirements.txt" not in content or "skill_seeker_mcp/requirements.txt" in content, \
  158. "GitHub workflow should use correct MCP paths"
  159. def test_readme_references_correct_paths(self):
  160. """Test that README references correct MCP paths"""
  161. readme = Path("README.md")
  162. if readme.exists():
  163. with open(readme, 'r') as f:
  164. content = f.read()
  165. # Check for old mcp/ directory paths (but allow mcp.json and "mcp" package name)
  166. # Use negative lookbehind to exclude skill_seeker_mcp/
  167. old_mcp_refs = re.findall(r'(?<!skill_seeker_)mcp/(server\.py|requirements\.txt)', content)
  168. if len(old_mcp_refs) > 0:
  169. pytest.fail(f"README references old mcp/ directory: {old_mcp_refs}")
  170. def test_documentation_references_correct_paths(self):
  171. """Test that documentation files reference correct MCP paths"""
  172. doc_files = list(Path("docs/").glob("*.md")) if Path("docs/").exists() else []
  173. for doc_file in doc_files:
  174. with open(doc_file, 'r') as f:
  175. content = f.read()
  176. # Check for old mcp/ directory paths (but allow mcp.json and "mcp" package name)
  177. old_mcp_refs = re.findall(r'(?<!skill_seeker_)mcp/(server\.py|requirements\.txt)', content)
  178. if len(old_mcp_refs) > 0:
  179. pytest.fail(f"{doc_file} references old mcp/ directory: {old_mcp_refs}")
  180. def test_mcp_directory_structure():
  181. """Test that MCP directory structure is correct (new src/ layout)"""
  182. mcp_dir = Path("src/skill_seekers/mcp")
  183. assert mcp_dir.exists(), "src/skill_seekers/mcp/ directory should exist"
  184. assert mcp_dir.is_dir(), "src/skill_seekers/mcp should be a directory"
  185. assert (mcp_dir / "server.py").exists(), "src/skill_seekers/mcp/server.py should exist"
  186. assert (mcp_dir / "__init__.py").exists(), "src/skill_seekers/mcp/__init__.py should exist"
  187. # Old directories should NOT exist
  188. old_mcp = Path("mcp")
  189. old_skill_seeker_mcp = Path("skill_seeker_mcp")
  190. if old_mcp.exists():
  191. # If it exists, it should not contain server.py (might be leftover empty dir)
  192. assert not (old_mcp / "server.py").exists(), \
  193. "Old mcp/server.py should not exist - migrated to src/skill_seekers/mcp/"
  194. if old_skill_seeker_mcp.exists():
  195. assert not (old_skill_seeker_mcp / "server.py").exists(), \
  196. "Old skill_seeker_mcp/server.py should not exist - migrated to src/skill_seekers/mcp/"
  197. if __name__ == '__main__':
  198. print("=" * 60)
  199. print("Testing Setup Scripts")
  200. print("=" * 60)
  201. pytest.main([__file__, "-v"])